wissel.net

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

The quick and dirty Domino Cloudant export


Moving data out of Domino never has been hard with all the APIs available. The challenge always has been: move them where? Ignoring for a second all security considerations, the challenge is to find a target structure that matches the Domino model. Neither flat table storage nor RDBMS fit that very well.
A close contender is MongoDB which is used in one compelling Notes retirement offering. However the closest match in concept and structure is Apache CouchDB, not surprisingly due to its heritage and origin.
It is maintained by a team led by the highly skilled Jan Lehnardt and of course there are differences to Notes.
But the fit is good enough. Using the lightweight Java library Ektorp exporting a set of documents from Notes to CouchDB is a breeze. The core class is a simple mapping of a Notes document to a JSON structure:
package com.notessensei.export;

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

import lotus.domino.Document;
import lotus.domino.Item;
import lotus.domino.NotesException;

public class NotesJsonDoc {
	public static String ID_FIELD = "_id";
	public static String REV_FIELD = "_rev";
	
	private Map<String, String> content = new HashMap<String, String>();
	
	@SuppressWarnings("rawtypes")
	public NotesJsonDoc(Document source) throws NotesException {
		Vector allItems = source.getItems();
		for (Object itemObject : allItems) {
			Item item = (Item) itemObject;
			this.content.put(item.getName(), item.getText());
		}
		this.content.put(ID_FIELD, source.getUniversalID());
	}
	
	public Map<String, String> getContent() {
		return this.content;
	}
	
	public NotesJsonDoc setRevision(String revision) {
		this.content.put(REV_FIELD, revision);
		return this;
	}
	
	public String getId() {
		return this.content.get(ID_FIELD);
	}
}

The uniqueID used in Notes can be used as the ID in CouchDB, thus make it easy to even update documents both ways if required. In your agent you need to create a Java collection consisting of the unids of the documents you want to export. You could do that using a view or the document collection. Here you go:
package com.notessensei.export;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Collection;
import java.util.Map;
import java.util.Vector;

import org.ektorp.AttachmentInputStream;
import org.ektorp.CouchDbConnector;
import org.ektorp.CouchDbInstance;
import org.ektorp.http.HttpClient;
import org.ektorp.http.StdHttpClient;
import org.ektorp.impl.StdCouchDbInstance;

import lotus.domino.EmbeddedObject;
import lotus.domino.NotesException;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.Document;


public class DataUploader {

	private CouchDbConnector getDB(String protocol, String credentials, String targetURL, String couchDBName)
			throws MalformedURLException {
		try {
			HttpClient httpClient = new StdHttpClient.Builder().url(protocol + credentials + targetURL).build();
			CouchDbInstance dbInstance = new StdCouchDbInstance(httpClient);
			// if the second parameter is true, the database will be created if
			// it doesn't exists
			CouchDbConnector db = dbInstance.createConnector(couchDBName, true);
			return db;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public void uploadData(Session s, Database nsf, Collection<String> unids, String protocol, String credentials,
			String targetURL, String couchDBName) throws MalformedURLException {
		CouchDbConnector db = this.getDB(protocol, credentials, targetURL, couchDBName);
		String dbURL = protocol + targetURL + "/" + couchDBName + "/";

		for (String unid : unids) {
			Document doc = null;
			try {
				doc = nsf.getDocumentByUNID(unid);
				this.uploadOneDocument(s, dbURL, doc, db);
			} catch (NotesException e) {
				e.printStackTrace();
			} finally {
				try {
					if (doc != null) {
						doc.recycle();
					}
				} catch (NotesException e) {
					// no action required
				}
			}
		}
	}

	@SuppressWarnings("rawtypes")
	private void uploadOneDocument(Session s, String dbURL, Document doc, CouchDbConnector db) throws NotesException {
		String unid = doc.getUniversalID();
		String docURL = dbURL + unid;
		NotesJsonDoc payLoad = new NotesJsonDoc(doc);
		// Capture the directURL to the JSON document
		payLoad.getContent().put("docURL", docURL);
		
		// Create document
		db.create(payLoad.getContent());
		
		// Handle attachments
		try {
			if (doc.hasEmbedded()) {
				Vector aNames = s.evaluate("@AttachmentNames", doc);
				if (!aNames.isEmpty()) {
					Map existingDoc = db.get(Map.class, payLoad.getId());
					String rev = existingDoc.get(NotesJsonDoc.REV_FIELD).toString();
					for (Object aNameObj : aNames) {
						this.uploadAttachment(doc, db, rev, aNameObj.toString());
					}
				}
			}
		} catch (NotesException e) {
			e.printStackTrace();
		}
	}

	/*
	 * Uploads attachments to the Cloudant database
	 */
	private void uploadAttachment(Document doc, CouchDbConnector db, String rev, String attName) {
		try {
			EmbeddedObject embObj = doc.getAttachment(attName);
			String attMime = this.getMimeType(attName);
			InputStream embeddedInput = embObj.getInputStream();
			AttachmentInputStream attStream = new AttachmentInputStream(attName, embeddedInput, attMime);
			db.createAttachment(doc.getUniversalID(), rev, attStream);
			embeddedInput.close();
			embObj.recycle();
		} catch (NotesException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/*
	 * Returns the mime type based on the extension only since we don't have a
	 * file on the file system
	 */
	private String getMimeType(String attName) {
		String ext = attName.substring(attName.lastIndexOf(".") + 1);
		return MimeTypes.getMimeType(ext);
	}
}

The quick and dirty part: no handling of MIME, duplicate attachment names, embedded objects etc. So - as usual YMMV

Posted by on 21 January 2016 | Comments (1) | categories: Bluemix

Comments

  1. posted by Mark Barton on Thursday 21 January 2016 AD:
    Another reason for CouchDB is PouchDB which gives you the ability to go offline and replicate - handy when trying to reproduce a Notes client feature. Emoticon biggrin.gif