wissel.net

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

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();
        nc.runUntilKeyPresses("q");
        System.exit(0);
    }

    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();
        options.setPort(NotesClient.listenport);
        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);

        this.startListening();

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

        this.stopListening();

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

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

    private void stopListening() {
        this.hs.close();
        this.de.shutdown();
        AppConfig.INSTANCE.save();
    }
}

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;
        this.setSessionType(DominoSessionType.NATIVE);
    }

    @Override
    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)");
                this.de.execute(this);
                // 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]);
                this.de.execute(this);
            } /* 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>");
        txt.append("<h2>");
        txt.append(req.uri());
        txt.append("</h2>");
        System.out.println("Got request: " + req.uri());
        txt.append("</body></html>");
        resp.end(txt.toString());
    }

    @Override
    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());
        b.startObject("documents");

        for (ViewEntry ve : vn) {
            b.startObject(ve.getUniversalID());
            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());
                }
            }
            b.endObject();
        }
        b.endObject();
        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"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.notessensei.vertx</groupId>
  <artifactId>com.notessensei.vertx.notes</artifactId>
  <version>0.0.2-SNAPSHOT</version>
  <name>IBM Notes 9.0 vert.x 3.0 client</name>
  <description>Local web</description>
  <repositories>
  <repository>
    <id>sonatype-staging</id>
    <url>https://oss.sonatype.org/content/repositories/staging/</url>
  </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-core</artifactId>
      <version>3.0.0-preview1</version>
    </dependency>
    <dependency>
    	<groupId>com.ibm.icu</groupId>
    	<artifactId>icu4j</artifactId>
    	<version>53.1</version>
    </dependency>
    <dependency>
    	<groupId>com.google.code.gson</groupId>
    	<artifactId>gson</artifactId>
    	<version>2.3</version>
    </dependency>
    <dependency>
    	<groupId>com.google.code.findbugs</groupId>
    	<artifactId>jsr305</artifactId>
    	<version>3.0.0</version>
    </dependency>
    <dependency>
    	<groupId>org.ow2.asm</groupId>
    	<artifactId>asm-all</artifactId>
    	<version>5.0.3</version>
    </dependency>
    <dependency>
    	<groupId>com.google.guava</groupId>
    	<artifactId>guava</artifactId>
    	<version>18.0</version>
    </dependency>
    <dependency>
    	<groupId>io.reactivex</groupId>
    	<artifactId>rxjava</artifactId>
    	<version>1.0.0-rc.3</version>
    </dependency>
    <dependency>
    	<groupId>javassist</groupId>
    	<artifactId>javassist</artifactId>
    	<version>3.12.1.GA</version>
    </dependency>
    <dependency>
    	<groupId>javolution</groupId>
    	<artifactId>javolution</artifactId>
    	<version>5.5.1</version>
    </dependency>
    <dependency>
    	<groupId>com.ibm.sbt</groupId>
    	<artifactId>com.ibm.commons</artifactId>
    	<version>9.0.0</version>
    </dependency>
    <dependency>
    	<groupId>io.vertx</groupId>
    	<artifactId>vertx-hazelcast</artifactId>
    	<version>3.0.0-preview1</version>
    </dependency>
  </dependencies>
</project>

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

Comments

  1. No comments yet, be the first to comment