Search

Domino Upgrade

VersionSupport end
5.0
6.0
6.5
7.0
8.0
8.5
Upgrade to 9.x now!
(see the full Lotus lifcyle) To make your upgrade a success use the Upgrade Cheat Sheet.
Contemplating to replace Notes? You have to read this! (also available on Slideshare)

Languages

Other languages on request.

Twitter

Useful Tools

Get Firefox
Use OpenDNS
The support for Windows XP has come to an end . Time to consider an alternative to move on.

About Me

I am the "IBM Collaboration & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.
Reach out to me via:
Follow notessensei on Twitter
(posts)
Skype
Sametime
IBM
Facebook
LinkedIn
XING
Amazon Store
Amazon Kindle
NotesSensei's Spreadshirt shop
profile for stwissel on Stack Exchange, a network of free, community-driven Q&A sites

« Metawork, nobody is capable but all participate grudgingly | Main| The totally inofficial guide to Verse on Premises »

Domino meets RXJava

Verse on premises (VoP) is nearing its second beta release and fellow Notes experts are wondering if they need to install Apache Solr as part of the VoP deployment. There was a lengthy, high quality discussion and quite some effort evaluating alternatives. In conclusion it was decided to deliver the subset of Solr capabilities needed for VoP as series of OSGi plugins to the Domino server. The project was formed out of the experience with ProjectCastle, which continues as Project OrangeBox to deliver these plugins. In VoP you might encounter one or the other reference to PoB, so now you know where it comes from.
One of the design challenges to solve was to emulate the facet results of the Solr search engine. I build some prototypes and finally settled on the use of RxJava.
RxJava is a member of the ReactiveX programming family, which is designed around the Observer pattern, iterators and functional programming. Check out the main site to get into the groove.
The task at hand is to convert something Domino (a ViewNavigator, a DocumentCollection or a Document) into something that emits subscribable events. I started with turning a document into an NotesItem emitter. Purpose of this was the creation of lighweight Java objects that contain the items I'm interested in. Since Domino's Java has special needs and I couldn't use the ODA, special precaution was needed.
There are plenty of methods to create an Observable and on first look Create looks most promising, but it left the question of recycling open. Luckily there is the Using method that creates a companion object that lives along the Observable and gets explicitly called when the Observable is done. To create the NotesItem emitting Observable I settled on the From method with an Iterable as source. The moving parts I had to create were class DocumentSource implements Iterable<Item> and class ItemIterator implements Iterator<Item>
Why Reactive? In a nutshell: a source emits data and any number of subscribers can subscribe to. Between the emission and subscription any number of filters, modifiers and aggregators can manipulate the data emitted. Since each of them lives in its own little class, testing and composition become very easy. Let's look at an example:
docSource.getItemStream(session).filter(nameFilter).map(toPobItem).map(nameMapper).subscribe(new ItemAdder());
You almost can read this aloud: "The source emits a stream of items, they get filtered by Name, then converted into another Java object (PobItem) and renamed before added to the subscriber.". In a different case you might want to collect all entities (users, groups, roles) that have access to a document, you migh create a "readerAuthorFilter". The individual classes are very easy to test. E.g. the Name filter looks like this:
// requiredFields is is a Collection<String> of NotesItem names to include or exclude
ItemNameFilter nameFilter = new ItemNameFilter(requiredFields, ItemNameFilter.FilterMode.INCLUDE);

public class ItemNameFilter implements Func1<Item, Boolean> {

    public enum FilterMode {
        INCLUDE, EXCLUDE;
    }

    private final Logger      logger      = Logger.getLogger(this.getClass().getName());
    private final Set<String> itemNameSet = new HashSet<String>();
    private final FilterMode  filterMode;

    /**
     * Flexible include or exclude
     * 
     * @param itemNames
     *            Collection of Names to include or exclude
     * @param filterMode
     *            INCLUDE or EXCLUDE
     */
    public ItemNameFilter(Collection<String> itemNames, FilterMode filterMode) {
        this.filterMode = filterMode;
        this.updateItemNames(itemNames);
    }

    public ItemNameFilter(Collection<String> itemNames) {
        this.filterMode = FilterMode.INCLUDE;
        this.updateItemNames(itemNames);
    }

    private void updateItemNames(Collection<String> itemNames) {
        this.itemNameSet.addAll(itemNames);
    }

    @Override
    public Boolean call(Item incomingItem) {
        // Include unless proven otherwise
        boolean result = true;
        try {
            String itemName = incomingItem.getName();
            boolean inList = this.itemNameSet.contains(itemName.toLowerCase());
            result = (inList && this.filterMode.equals(FilterMode.INCLUDE));
        } catch (NotesException e) {
            this.logger.log(Level.SEVERE, e);
            result = false;
        }
        return result;
    }
}

The two classes are not very long and, once you are comfortable with Reactive, easy to read:

ItemIterator

public class ItemIterator implements Iterator<Item> {
    @SuppressWarnings("rawtypes")
    private final Vector theNotesItems;
    private int          itemCount = 0;
    private Item         currentItem = null;

    @SuppressWarnings("rawtypes")
    public ItemIterator(Vector vector) {
        this.theNotesItems = vector;
    }

    @Override
    public boolean hasNext() {
        return ((!this.theNotesItems.isEmpty()) && (this.itemCount < this.theNotesItems.size()));
    }

    @Override
    public Item next() {
        // Recycle the current item. Shred takes care of null values
        Utils.shred(this.currentItem);
        his.currentItem = (Item) this.theNotesItems.get(this.itemCount);
        this.itemCount++;
        return result;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

DocumentSource

public class DocumentSource implements Iterable<Item> {
    private final Logger logger = Logger.getLogger(this.getClass().getName());
    private Document     doc    = null;
    private final String notesURL;

    public DocumentSource(final Session session, final Document doc) throws SourceNotAvailableException {
        this.doc = doc;
        try {
            this.notesURL = doc.getNotesURL();
        } catch (final NotesException e) {
            Utils.logError(this.logger, e);
            throw new SourceNotAvailableException("Can't retrieve NotesURL");
        }
    }

    /**
     * RxJava Observable access to all Items in a Notes Document
     *
     * @param session
     *            A Domino session to access the document if needed
     * @return a Observable with NotesItems
     */
    public Observable<Item> getItemStream(final Session session) {

        final Observable<Item> itemStream = Observable.using(new Func0<DocumentSource>() {
            @Override
            public DocumentSource call() {
                return DocumentSource.this;
            }
        }, new Func1<DocumentSource, Observable<Item>>() {
            @Override
            public Observable<Item> call(final DocumentSource documentSource) {
                return Observable.from(documentSource);
            }
        },

        new Action1<DocumentSource>() {
            @Override
            public void call(final DocumentSource documentSource) {
                documentSource.tearDown();
            }
        });

        return itemStream;
    }

    public String getNotesURL() {
        return this.notesURL;
    }

    @Override
    @SuppressWarnings("rawtypes")
    public Iterator<Item> iterator() {
        Vector result;
        try {
            result = this.doc.getItems();
        } catch (final NotesException e) {
            Utils.logError(this.logger, e);
            // Using an empty vector instead
            result = new Vector();
        }
        return new ItemIterator(result);
    }

    /**
     * Cleaning up a document source to free up the memory
     * allocated in the C API
     */
    protected void tearDown() {
        Utils.shred(this.doc);
        this.doc = null;
    }
}

I will report more on my RXJava meets Domino experience. As usual YMMV

Comments

Gravatar Image1 - It's a shame you can't use the ODA. We've bundled RxJava with it for years now, and I've been waiting for someone to get around to exploiting it. Item iterators are interesting, but we have some advanced functions like NoteLists that could do some amazing things in conjunction with Reactive.

Disclaimer

This site is in no way affiliated, endorsed, sanctioned, supported, nor enlightened by Lotus Software nor IBM Corporation. I may be an employee, but the opinions, theories, facts, etc. presented here are my own and are in now way given in any official capacity. In short, these are my words and this is my site, not IBM's - and don't even begin to think otherwise. (Disclaimer shamelessly plugged from Rocky Oliver)
© 2003 - 2017 Stephan H. Wissel - some rights reserved as listed here: Creative Commons License
Unless otherwise labeled by its originating author, the content found on this site is made available under the terms of an Attribution/NonCommercial/ShareAlike Creative Commons License, with the exception that no rights are granted -- since they are not mine to grant -- in any logo, graphic design, trademarks or trade names of any type. Code samples and code downloads on this site are, unless otherwise labeled, made available under an Apache 2.0 license. Other license models are available on written request and written confirmation.