Usability - Productivity - Business - The web - Singapore & Twins

By Date: September 2014

Put an angular face on your inbox

In the last installment I got vert.x to emit a Notes view, read from your local mail file, to be emitted as JSON stream. While that might be perfectly fine for the inner geek, normal mortals want to look (and interact) with something more pretty.
The cool kids on the block for web interfaces and applications are Twitter BootStrap and AngularJS, so these will be the tools in this installment.
Not familiar with them? Go and watch some videos. Back? Let's get going.
Since I'm not much of a designer, I choose the ready made Admin LTE template based on bootstrap. The main reason for choosing this one, was a timeline layout that I wanted for the inbox.
My inbox is already sorted by date, so it should fit nicely (so I thought). However the view optically is categorized by date and under the hood just a flat list of <li> elements. A little tweaking was necessary. The result looks quite OK for an after hour exercise:
Alternate layout for the Inbox
The steps to get there:
  1. Tweak the vert.x service to render a categorized by date version of the inbox. You can do that without touching the underlying view. I'll provide details on that modules in a later article (and some sample data below). The main difference for now: the inbox data will be available at the URL /notes/categorized/($Inbox)
  2. Create templates and directives for Angular
  3. Create the Angular.js app and its Controllers
Of course, no application is complete with a nice set of challenges. The biggest here was the flat list for the time line. I tried to adjust the CSS to accommodate a hierarchical list, where the outer elements are the date containing all messages arrived at that day, but there was too much CSS. So I decided to tweak the output a little.

Read more

Posted by on 30 September 2014 | Comments (0) | categories: IBM Notes JavaScript Software WebDevelopment

Rendering a Notes view as JSON REST service - on your client

My next goal after getting the basic connection to Notes working is to be able to serve a potential API. Still making friends with the non-blocking approach of vert.x, I'm taking baby steps forward. In this round I want to be able to deliver a view or folder as JSON string. On a Domino server that is easy. You can use ?ReadViewEntries&OutputFormat=JSON. On a Notes client you have to do it yourself.
In round one I will ignore categorized views (that's for the next time), but I already will massage the JSON to be leaner. After all why send it over the wire what you don't need. So I have a little AppConfig.INSTANCE singleton, that delivers a viewConfig object. This object has the list of columns and the inteded labels that I want to be returned.
Since last time some of the libraries have been updated and I'm now running vert.x 3.0.0.Preview1 and the OpenNTF Domino API RC2. I unpacked the OpenNTF release and removed the Jar files and replaced them with Maven dependencies. This step isn't necessary, but I'm expanding my Maven knowledge, so it was good practise. The starter application looks quite simple:

package com.notessensei.vertx.notes;

import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import java.io.IOException;
import org.openntf.domino.thread.DominoExecutor;

public class NotesClient {

     * @param args
     * @throws IOException
    public static void main(String[] args) throws IOException {
        final NotesClient nc = new NotesClient();

    private static final int     listenport        = 8110;
    private static final int     dominothreadcount = 10;
    private final Vertx          vertx;
    private final HttpServer     hs;
    private final DominoExecutor de;

    public NotesClient() throws IOException {
        this.vertx = Vertx.factory.vertx();
        final HttpServerOptions options = HttpServerOptions.options();
        this.hs = this.vertx.createHttpServer(options);
        this.de = new DominoExecutor(NotesClient.dominothreadcount);

    public void runUntilKeyPresses(String keystring) throws IOException {
        int quit = 0;
        final int quitKey = keystring.charAt(0);


        while (quit != quitKey) { // Wait for a keypress
            System.out.print("Notes Client Verticle started, version ");
            System.out.print("Started to listen on port ");
            System.out.print("Press ");
            System.out.println("<Enter> to stop the Notes Client Verticle");
            quit = System.in.read();


        System.out.println("\n\nNotes Client Verticle terminated!");

    private void startListening() {
        final Handler<HttpServerRequest> h = new NotesRequestHandler(this.de);

    private void stopListening() {

The Notes request handler, checks what is requested and renders the view into JSON using a "homegrown" JSONBuilder which I designed similar to a SAX writer.

package com.notessensei.vertx.notes;

import java.util.Map;

import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;

import org.openntf.domino.Database;
import org.openntf.domino.Session;
import org.openntf.domino.View;
import org.openntf.domino.ViewEntry;
import org.openntf.domino.ViewNavigator;
import org.openntf.domino.thread.AbstractDominoRunnable;
import org.openntf.domino.thread.DominoExecutor;
import org.openntf.domino.thread.DominoSessionType;

public class NotesRequestHandler extends AbstractDominoRunnable implements Handler<HttpServerRequest> {

    private static final long           serialVersionUID = 1L;
    private transient HttpServerRequest req;
    private ViewConfig                  viewConfig       = null;
    private final DominoExecutor        de;

    public NotesRequestHandler(DominoExecutor de) {
        this.de = de;

    public void run() {
        Session s = this.getSession();
        HttpServerResponse resp = this.req.response();
        this.renderInbox(s, resp);

    public void handle(HttpServerRequest req) {
        HttpServerResponse resp = req.response();

        String path = req.path();

        String[] pathparts = path.split("/");
        // The request must have notes in the URL
        if (pathparts.length < 3 || !pathparts[1].equals("notes")) {
            this.sendEcho(req, resp);
        } else {
            this.req = req;
            // Parameter 3 is either view or inbox
            // if it is inbox, we pull in the inbox
            if (pathparts[2].equals("inbox")) {
                this.viewConfig = AppConfig.INSTANCE.getViewConfig("($Inbox)");
                // if it is view we pull the respective view
            } else if (pathparts.length > 3 && pathparts[2].equals("view")) {
                this.viewConfig = AppConfig.INSTANCE.getViewConfig(pathparts[3]);
            } /*more here*/ else {
                // Nothing value, so we send an check only
                this.sendEcho(req, resp);

    private void renderInbox(Session s, HttpServerResponse resp) {
        resp.headers().set("Content-Type", "application/json; charset=UTF-8");
        Database mail = s.getMailDatabase();
        resp.end(this.renderView(mail, this.viewConfig));

    private void sendEcho(HttpServerRequest req, HttpServerResponse resp) {
        StringBuilder txt = new StringBuilder();
        resp.headers().set("Content-Type", "text/html; charset=UTF-8");
        txt.append("<html><body><h1>Notes request handler</h1>");
        System.out.println("Got request: " + req.uri());

    public boolean shouldStop() {
        // TODO Auto-generated method stub
        return false;

    private String renderView(Database db, ViewConfig vc) {
        JsonBuilder b = new JsonBuilder();
        View view = db.getView(vc.getViewName());
        ViewNavigator vn = view.createViewNav();

        b.addValue("count", vn.getCount());
        b.addValue("name", vc.getViewName());

        for (ViewEntry ve : vn) {
            b.addValue("position", ve.getPosition());
            b.addValue("isRead", ve.getRead());
            Map<String, Object> entries = ve.getColumnValuesMap();
            for (Map.Entry<String, Object> entry : entries.entrySet()) {
                String key = vc.isEmpty() ? entry.getKey() : vc.getColumnName(entry.getKey());
                if (key != null) {
                    b.addValue(key, entry.getValue());
        return b.toString();
The JSONWriter class needs some more polish to get date formatting right and to keep track of nesting depth, but it should perform well.

What grew quite a bit is the Maven POM model (Maven is to Java roughly what npm is to Node.js or bauer to browser projects):

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <name>IBM Notes 9.0 vert.x 3.0 client</name>
  <description>Local web</description>

Next stops (in no specific order):
  • Merge documents from 2 or more views - use case: your mail file and one or more archives
  • Render categorized views as nested JSON objects
  • Have a HTML UI to look at the list and the individual documents
  • Search using Apache Solr
  • Folder predictions using AFsNc
Stay tuned!

Posted by on 25 September 2014 | Comments (0) | categories: Lotus Notes vert.x

Keeping up with all the GIT

Unless you stuck in the last century, you might have noticed, that the gold standard for version control is GIT. Atlassian likes it, IBM DevOps supports it and of course the Linux Kernel is build with it.
The prime destination for opensource projects is GitHub, with BitBucket coming in strong too. Getting the code of a project you work with (and I bet you do - jquery anyone) is just a git clone away. Of course that opens the challenge to keep up with all the changes and updates. While in the projects you work on, a branch, pull and push is daily work - using the command line or a nice UI. For that "keep an eye one" projects this gets tedious quite fast.
I'm using a little script (you even could cron it or attach it to a successful network connection) to keep all my "read-only" repositories up-to-date. You need to change the basedir= to match your path. Enjoy

# !/bin/sh
# Helper script to keep all the things I pulled from GITHUB updated
# most of them are in ~/github, but some are somewhere else

# Pulls a repository from GIT origin or Mercurial
syncrep() {
 echo "Processing $f file..."
 cd $1
 isHG=`find -maxdepth 1 -type d -name ".hg"`
 if [ -n "$isHG"]
  git pull origin master &
  echo "$f is a Mercurial directory"
  hg pull


# Part 1: all in ~/github
notify-send -t 20000 -u low -i gtk-dialog-info "Starting GIT threaded update"
for f in $FILES
 syncrep $f

# Part 2: all in ~/company
notify-send -t 20000 -u low -i gtk-dialog-info "Starting COMPANY threaded update"
for f in $FILES
 syncrep $f

cd ~
notify-send -t 20000 -u low -i gtk-dialog-info "All GIT pull requests are on their way!"

# Wait for the result
while [ "$stillrunning" -eq "0" ]
 sleep 60
 pgrep git- > /dev/null
notify-send -t 20000 -u low -i gtk-dialog-info "GIT pull updates completed"

A little caveat: when you actively work on a repository you might not want the combination origin - master, so be aware: as usual YMMV

Posted by on 25 September 2014 | Comments (0) | categories: Software

Collaboration in context

Harry, a storm is coming, at least if you follow Cary Youman. Nothing less that the way we collaborate will be, again, a focus for IBM. The need has not found a definite solution. The attempt to reinvent eMail is starving in the incubator. Great minds try to reinvent the conversation (and looks suspiciously like Wave). So what is so tricky about collaboration?
In short it is context, the famous 5 W. In our hyperconnected world context can get big rather fast:
Collaboration In Context
An eMail system usually provides limited context: From, When, Subject. Using Tools and Advanced Analytics modern systems try to spice that context. Other shoot the messenger without addressing the next level of problem: Flood vs. Scatter

Read more

Posted by on 24 September 2014 | Comments (0) | categories: Software

Creating nginx configurations for Domino SSL

Websites need to be secure, so the SHA-1 cipher is coming to an end. Despite best efforts, Domino is stuck with this outdated Cipher. While you can, on Windows, hide Domino behind IHS, I find nginx easier to tame.
Jesse explains how to configure nginx as the Domino proxy. So all is good, expecially since he also covered High availability.
But when you have a lot of sites, that's a lot of typing (and copy & paste from the Internet site documents). Mustache to the rescue! I've written about Mustache before and it suits the task quite nicely:
  1. Create one well working sample configuration
  2. Replace the site specific values with {{mustache}} variables
  3. Run it against all Internet site documents
The code I used (see below) generates just 4 variables:
  1. {{name}} The name of the site according to the configuration document. I use it here to configure the file name
  2. {{siteName}}The first web name, it will become the listen parameter
  3. {{allNames}} All web names, they will be listed as server_name
  4. {{settings}} all field values of the Internet site documents as concatenated strings. Using a dot notation they can be used directly. e.g. {{settings.SSLKeyFile}}. Using this approach you can do whatever is needed to generate your desired output
This is the initial template, based on Jesse's article:

server {
        listen {{siteName}}:443;
        server_name {{#allNames}} {{.}}{{/allNames}};
        client_max_body_size 100m;

        ssl on;
        # Original keyfile: {{settings.SSLKeyFile}}
        ssl_certificate      /etc/nginx/ssl/{{name}}.pem;
        ssl_certificate_key /etc/nginx/ssl/{{name}}.key;
        location / {
                proxy_read_timeout 240;
                proxy_pass http://localhost:8088;
                proxy_redirect off;
                proxy_buffering off;
                proxy_set_header        Host               $host;
                proxy_set_header        X-Forwarded-For    $proxy_add_x_forwarded_for;
                proxy_set_header        $WSRA              $remote_addr;
                proxy_set_header        $WSRH              $remote_addr;
                proxy_set_header        $WSSN              $host;
                proxy_set_header        $WSIS              True;


The Java code takes in the file name of that template as parameter, so when you feel you rather use Apache or need a different output (e.g. a report), you are free to supply a different file here.

Read more

Posted by on 20 September 2014 | Comments (1) | categories: IBM Notes nginx

Tracking down slow internet on SingTel Fibre to the home

SingTel makes big claims about the beauty of their fibre offering. I do not experience the claimed benefits. So I'm starting to track down what is happening. Interestingly when you visit SpeedTest, it shows fantastic results. I smell rat.
So I ran a test with Pocketinet in Walla Walla, WA. SpeedTest claims a 5ms ping response, but when I, immediate before or after such a test, issue a ping -c5 www.pocketinet.com I get results rather in the range of 200-230ms.
Ein Schelm wer böses dabei denkt!
While this evidence isn't strong enough, to accuse someone of tampering, it points to the need to investigate why the results are so different ( IDA are you listening?). So I started looking a little deeper. Using traceroute with the -I parameter (that uses the same packets as ping) I checked a bunch of websites. Here are the results (I stripped out the boring parts):
traceroute -I www.pocketinet.com
  9  3.913ms  3.919ms  4.033ms
 10  204.256ms  170.493ms  171.314ms

traceroute -I www.economist.com
  9  4.316ms  4.882ms  4.680ms
 10  193.164ms  188.148ms  196.526ms

traceroute -I www.cnn.com
  9  4.772ms  4.679ms  5.160ms
 10  171.006ms  187.336ms  171.447ms

traceroute -I www.ibm.com
 9  4.385ms  5.857ms  3.853ms
 10  178.135ms  183.842ms  181.097ms

Something is rotten in the state of Denmark international connectivity! (Sorry Wil)
Only Google bucks that pattern. But that's due to the fact that their DNS sends me to a server in Singapore. So the huge jump in latency happens in the 203.208.182._and 203.208.151._ subnets.
whois tells me:
% [whois.apnic.net]
% Whois data copyright terms    http://www.apnic.net/db/dbcopyright.html

% Information related to ' -'

inetnum: -
netname:        SINGTEL-IX-AP
descr:          Singapore Telecommunications Pte Ltd

So, the servers might be in the SingTel overseas location? InfoSniper sees them in Singapore (you can try others with the same result). Now I wonder, is the equipment undersized, wrongly configured or something else happening that takes time on that machine?
Looking for an explanation.

Update 19 Sep 2014: Today the speedtest.net site shows ping results that are similar to traceroute/ping from the command line - unfortunately not due to the fact that the command line results got better, but the speedtest.net results worse - Now ranging in the 200ms. I wonder what happened. Checking the effective up/down speed will be a little more tricky. Stay tuned

Posted by on 17 September 2014 | Comments (2) | categories: Buying Broadband

Foundation of Software Development

When you learn cooking, there are a few basic skills that need to be in place before you can get started: cutting, measuring, stiring and understanding of temperature's impact on food items. These skills are independent from what you want to cook: western, Chinese, Indian, Korean or Space Food.
The same applies to software development. Interestingly we try to delegate these skills to ui designers, architects, project managers analyst or infrastructure owners. To be a good developer, you don't need to excel in all of those skills, but at least develop a sound understanding of the problem domain. There are a few resources that I consider absolute essential: All these resources are pretty independent from what language, mechanism or platform you actually use, so they provide value to anyone in the field of software development.
As usual YMMV

Posted by on 12 September 2014 | Comments (1) | categories: Software

Flow, Rules, Complexity and Simplicity in Workflow

When I make the claim " Most workflows are simple", in return I'm hit with bewildered looks and the assertion: " No, ours are quite complex". My little provocation is quite deliberate, since it serves as an opening gambit to discuss the relation between flow, rules and lookups. All workflows begin rather simple. I'll take a travel approval workflow as a sample (resemblance of workflows of existing companies would be pure coincidence).
The workflow as described by the business owner
The explanation is simple: " You request approval from management, finance and compliance. When all say OK, you are good to go". Translating them into a flow diagram is quite easy:
With a simple straight line, one might wonder what is the fuzz about workflow systems and all the big diagrams are all about. So when looking closer, each box "hides" quite some complexity. Depending on different factors some of the approval are automatic. " When we travel to a customer to sign a sizable deal and we are inside our budget allocation, we don't need to ask finance". So the workflow looks a little more complex actually:
Different levels need different approvals
The above diagram shows the flow in greater detail, with a series of decision points, both inside a functional unit (management, finance and compliance) as well as between them. The question still stays quite high level. "required Y/N", "1st line manager approves". Since the drawing tools make it easy - and it looks impressive - the temptation to draw further lures strongly. You could easily model the quest for the manager inside a workflow designer. The result might look a little like this:

Read more

Posted by on 03 September 2014 | Comments (1) | categories: Workflow

Rethinking the MimeDocument data source

Tim (we miss you) and Jesse had the idea to store beans in Mime documents, which became an OpenNTF project.
I love that idea and was musing how to make it more "domino like". In its binary format, a serialized bean can't be used for showing view data, nor can one be sure that it can be transported or deserialized other than through the same class version as the creator (this is why Serialized wants to have a serialid).
With a little extra work, that becomes actually quite easy: Enter JAXB. Serializing a bean to XML (I hear howling from the JSON camp) allows for a number of interesting options:
  • The MIME data generated in the document becomes human readable
  • If the class changes a litte de-serialization will still work, if it changes a lot it can be deserialized to an XML Document
  • Values can be extracted using XPath to write them into the MIME header and/or regular Notes items - making it accessible for use in views
  • Since XML is text, full text search will capture the content
  • Using a stylesheet a fully human readable version can be stored with the original MIME (good to eMail)
I haven't sorted out the details, but lets look at some of the building blocks. Who ever has seen me demo XPages will recognize the fruit class. The difference here: I added the XML annotations for a successful serialization:

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Fruit", namespace = "http://www.notessensei.com/fruits")
public class Fruit {
@XmlAttribute(name = "name")
private String name;
@XmlElement(name = "color")
private String color;
@XmlElement(name = "taste")
private String taste;
@XmlAttribute(name = "smell")
private String smell;

    public String getSmell() {
        return this.smell;

    public void setSmell(String smell) {
        this.smell = smell;

    public Fruit() {
        // Default constructor

    public Fruit(final String name, final String color, final String taste, final String smell) {
        this.name = name;
        this.color = color;
        this.taste = taste;
        this.smell = smell;

    public final String getColor() {
        return this.color;

    public final String getName() {
        return this.name;

    public final String getTaste() {
        return this.taste;

    public final void setColor(String color) {
        this.color = color;

    public final void setName(String name) {
        this.name = name;

    public final void setTaste(String taste) {
        this.taste = taste;


The function (probably in a manager class or instance) to turn that into a Document is quite short. The serialization of JAXB would allow to directly serialize it into a Stream or String, but we need the XML Document step to be able to apply the XPath.

public org.w3c.dom.Document getDocument(Fruit fruit) throws ParserConfigurationException, JAXBException {
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  DocumentBuilder db = dbf.newDocumentBuilder();
  org.w3c.dom.Document doc = db.newDocument();
  JAXBContext context = JAXBContext.newInstance(fruit.getClass());
  Marshaller m = context.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
  m.marshal(fruit, doc);
  return doc;

The little catch here: There is a Document in the lotus.domino package as well as in the org.w3c.dom package. You need to take care not to confuse the two. Saving it into a document including the style sheet (to make it pretty) is short too. The function provides the stylesheet as a w3c Document and the list of fields to extract as a key (the field) - value (the XPath) map. Something like this:

Read more

Posted by on 01 September 2014 | Comments (2) | categories: XPages

Bikepad SmartPhone mount review

This is my field impression of the Bikepad SmartPhone mount having used it for a few weeks on my Montague Paratrooper pro

TL:TRThe Bikepad is a highly functional accessory to keep your phone on your bike fully functional. Is has quality craftsmanship and a sleek design. If I had an editor-refuses-to-give-it-back award to give (I actually paid for it), I would award it.

I do cycle for longer durations and some rough spots, so I like to keep a phone in reach. Not at last to keep SWMBO updated. When I learned about Bikepad and their claim " Basically the Bikepad creates a vacuum between its surface and the device. The vacuum is strong enough to hold the device" I had to give it a try. Here's the verdict:
  • The Good
    Works as designed. The surface indeed creates a gecko feet like suction that firmly holds the phone in place. You actually need some force to pull it our. The aluminium base is sleek and solidly build. I like the minimal design: everything is there for it to function and nothing more, form follows function at its best. The half pipe shaped aluminium connector can be easily fixed on a stem (I didn't try handlebar mount) and secured with a Velcro. It comes with a foam pipe segment to adjust to different pipe diameters.
    I tried to shake the phone off in off-road or city conditions, including falling off the bike and hitting the ground (that part unplanned), but it stayed nice in place.
  • The Bad
    Works as designed. For the vacuum to build a close contact between phone and surface is needed. For your iShiny® (that was my main test unit) or a Nexus 4 (the other phone) that isn't a problem. For anything that doesn't have a flat surface or buttons on the back, you need to test it. Also the various phone cases need to be checked carefully. I tested a Otterbox case, which has a ridge running around the back. This prevents the case body from contact with the pad. Only the ridge has contact, which provides not enough suction
  • and The Ugly
    When it rains it pours. If the pad gets wet, it gets slippery and looses its suction. When the phone already sticks on it and it is rained on, it gradually will loose the grip. Luckily the pad comes with a little shower cap rain cover. With the cover it looks a little funny, not as cool anymore - but it does the job. Anyway you wouldn't want to expose your iShiny® to the bare elements. Another little challenge: my stem is quite thick, so the provided foam pipe is too think to squeeze between stem and half pipe. I was left with a little cutting exercise or alternative means. I opted for Sugru that holds everything in place
To see more, check out some of their videos. In summary: a keeper.

Posted by on 01 September 2014 | Comments (0) | categories: Cycling