wissel.net

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

Poking around the iNotes HTTP API (Part 2) - Fun with Rhino


The iNotes HTTP API wasn't designed for consumption outside the iNotes web client. This becomes apparent when looking at Form=l_GetOutline_JSON or Form=l_JSVars that return JavaScript and not JSON! The difference? JSON contains data only, while JavaScript contains function definitions too.
To make use of data included in the JavaScript there are 2 possibilities:
  • do some elaborate String operation
  • actually run JavaScript
Since Java 6, the JDK contains a JavaScript engine, that originally was developed as Mozilla Rhino. The most recent incarnation in Java 8 is Nashorn (which is German for Rhino). When you use an older JDK, think "older JS", nice functions like JSON.stringify or JSON.parse are missing and need to be added from elsewhere.
Turning the outline information into something Java useful we need to parse the returned JSON, lets see how that works. The Outline is a little more complex that it might seem. The JSON delivers 6 values (in an array) about the outline entries:
  1. The hierarchical sequence number. Something like 1 or 3.1 or 3.2.1 - depending on the level. The numbers have gaps where entries in the Outline are hidden from the web
  2. The hierarchy level starting with 0. So Asia\South-East\Singapore would be level 2
  3. The Label of the entry. Can be different from the actual name of the element
  4. The full path to the image for that entry. We'll strip that to get the name without a path, so we can use our own icons
  5. The full URL to that entry. It has the View Label (Name) and its UNID or system name in the string. We need the UNID for the unread count, so we'll extract that
  6. Can something be dragged there. 1 = Yes, it is a real folder, 0 = probably not, it is a view or a label only. "Probably" since e.g. you should be able to drag into the "Delete" view
Since that lends itself to some complexity, I created a OutlineInfo class that contains the OutlineEntry objects and that one gets filled in the JavaScript function.
Then the code turns out quite lean:

public OutlineInfo getOutlineInfo(String code, String outline) {
        ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
        engine.eval(code);
        Invocable jsFunc = (Invocable) engine;
        OutlineInfo result = (OutlineInfo) jsFunc.invokeFunction("getOutlineObject", outline);
        return result;
}

The String code contains our JavaScript function. In a working system I probably would keep the engine around somewhere to save the spinup time. outline contains what came back from the call to iNotes. The JavaScript code is equally lean:

function getOutlineObject(raw) {
 importPackage(com.notessensei.demo);
 var arr;
 eval("arr = "+raw);
 var result = new OutlineInfo();

    for (var line in arr.outline) {
     var oe = new OutlineEntry(arr.outline[line][0], arr.outline[line][1],
      arr.outline[line][2], arr.outline[line][3],
      arr.outline[line][4], arr.outline[line][5]);
     result.addEntry(oe);
    }

    return result;

}

Instead of OutlineInfo you could use a Java collection to return the value, so I won't list the class here. The OutlineEntry is more interesting, so you can run the sample yourself:

package com.notessensei.demo;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

public class OutlineEntry {

    public static final String DEFAULT_ICON = "outlineentry.gif";

    private final String id;
    private final int level;
    private final String label;
    private final String icon;
    private final String unid;
    private final String url;
    private final boolean isDropTarget;

    private int unreadCount = 0;

    // TODO: do I want it rather by sequence, then a TreeSet would be nice
    private final Set<OutlineEntry> children = new LinkedHashSet<OutlineEntry>();

    public OutlineEntry(String id, int level) {
        this.id = id;
        this.level = level;
        this.label = ">"+id;
        this.icon = DEFAULT_ICON;
        this.unid = null;
        this.url = null;
        this.isDropTarget = false;
    }

    public OutlineEntry(String id, int level, String label, String iconFullPath, String fullURL, int draggable) {
        this.id = id;
        this.level = level;
        this.label = label;
        this.icon = this.extractIcon(iconFullPath);
        this.unid = this.extractUNID(fullURL);
        this.url = fullURL;
        this.isDropTarget = (draggable > 0);
    }

    private String extractUNID(String fullURL) {
       if (fullURL==null || fullURL.equals("") || fullURL.indexOf("s_ViewName;")<0) {
        return null;
       }

       String workString = fullURL.substring(fullURL.indexOf("s_ViewName;")+11)+",";
       return workString.substring(0,workString.indexOf(","));

    }

    private String extractIcon(String iconFullPath) {
        if (iconFullPath == null || iconFullPath.equals("")) {
        return null;
        }
        String workString = iconFullPath + "/?OpenImageResource";
        String[] split = workString.substring(0,workString.indexOf("/?OpenImageResource")).split("/");
        return split[split.length-1];
    }

    public void addChild(OutlineEntry entry) {
        this.children.add(entry);
    }

    public void addChildren(Collection<OutlineEntry> children) {
       this.children.addAll(children);
    }

    public Collection<OutlineEntry> getChildren() {
       return this.children;
    }

    public String getIcon() {
        return this.icon;
    }

    public String getId() {
        return this.id;
    }

    public String getLabel() {
        return this.label;
    }

    public int getLevel() {
        return this.level;
    }

    public String getParent() {
        if (this.level == 0) {
            return null;
        }
        return this.id.substring(0, this.id.lastIndexOf("."));
    }

    public String getUNID() {
        return this.unid;
    }

    public int getUnreadCount() {
        return this.unreadCount;
    }

    public String getUrl() {
        return this.url;
    }

    public boolean isDropTarget() {
        return this.isDropTarget;
    }

    public void setUnreadCount(int unreadCount) {
        this.unreadCount = unreadCount;
    }

}

Now the challenge: provide a faster way to generate a collection of OutlineEntry objects! The code above takes on my system between 100 and 150ms

Posted by on 27 November 2014 | Comments (0) | categories: IBM Notes

Comments

  1. No comments yet, be the first to comment