Mindoo Blog - Cutting edge technologies - About Java, Lotus Notes and iPhone

  • Domino JNA Virtual Views: The Next Step in Domino Data Retrieval

    Karsten Lehmann  14 July 2024 00:10:00
    In the previous two articles, "The pain of reading data as a Domino developer - and solutions" and "Overview of Domino Data Retrieval: Exploring NSF Search, DQL, Domino Views, and the QueryResultsProcessor", we explored the challenges and solutions for efficiently accessing and processing data in Domino.

    Now, let's delve into the next evolution in Domino data handling.


    Version 0.9.49 of our
    Domino JNA open-source project introduced a new API called "Virtual Views", built with all the NIF, formula search, DQL, and QueryResultsProcessor history and limitations in mind.

    In short, you define the view structure as a VirtualView object in your Java code (columns with sorting, categorization, total, and average values) and then add one or more data providers that incrementally push data into the view (or remove it).


    This article will guide you through the innovative features and capabilities of the Virtual View API, demonstrating how it simplifies and enhances data retrieval and manipulation in Domino.


    Now, let's take a closer look at how you can leverage the Virtual View API to define and manage views dynamically in your Java code.


    To illustrate, consider the following code:


    Map someExternalData = new HashMap<>();
    someExternalData.put("Revoco", "Revoco Street 5, Los Angeles");

    someExternalData.put("Omnis", "Omnis Boulevard 12, New York");


    // by using "createViewOnce", we mark the view to be stored in memory,

    // as a version "1" and to auto discard it

    // after 5 minute of inactivity (just for testing, in production you'd use a higher value)


    // changing the version number to "2" would force a new view to be created


    VirtualView view = VirtualViewFactory.INSTANCE.createViewOnce("fakenames_namelengths",

           1,
    // version "1"
           5, TimeUnit.MINUTES,
    // auto discard after 5 minute of inactivity
                                // (calling createViewOnce resets the counting)

           (id) -> {

       return VirtualViewFactory.createView(

               new VirtualViewColumn("Lastname", "Lastname",

                       Category.YES, Hidden.NO, ColumnSort.ASCENDING, Total.NONE,

                       "Lastname"),


               new VirtualViewColumn("Firstname", "Firstname",

                       Category.NO, Hidden.NO, ColumnSort.ASCENDING, Total.NONE,

                       "Firstname"),


               new VirtualViewColumn("Total Name Length", "TotalNameLength",

                       Category.NO, Hidden.NO, ColumnSort.NONE, Total.SUM,

                       new VirtualViewColumnValueFunction< Integer >(1) {

                                              // this 1 is a version number for

                                              // the column function, might become

                                              // relevant later when we store the index to disk


                   @Override

                   public Integer getValue(String origin, String itemName,

                           INoteSummary columnValues) {

                       return columnValues.getAsString("Firstname", "").length() + 1 +

                               columnValues.getAsString("Lastname", "").length();

                   }

               }),


               new VirtualViewColumn("Average Name Length", "AverageNameLength",

                       Category.NO, Hidden.NO, ColumnSort.NONE, Total.AVERAGE,

                       new VirtualViewColumnValueFunction< Integer >(1) {



                   @Override

                   public Integer getValue(String origin, String itemName,

                           INoteSummary columnValues) {

                       return columnValues.getAsString("Firstname", "").length() + 1 +

                               columnValues.getAsString("Lastname", "").length();

                   }

               }),

               
               new VirtualViewColumn("Company Address", "CompanyAddress",

                       Category.NO, Hidden.NO, ColumnSort.NONE, Total.NONE,

                       new VirtualViewColumnValueFunction< String >(1) {


                           @Override

                           public String getValue(String origin, String itemName,

                                   INoteSummary columnValues) {

                               
    // poor man's JOIN :-)
                               // we fetch the company address from a map

                               // using the company name as key

                               
                               String companyName = columnValues.getAsString("CompanyName", "");

                               return someExternalData.getOrDefault(companyName, "");

                           }

                       }),


               new VirtualViewColumn("Last Update", "LastMod",

                       Category.NO, Hidden.NO, ColumnSort.NONE, Total.NONE,

                       "LastMod"),


               
    // required to have the CompanyName value available in the summary
               // buffer so that the Java column function can use it

               new VirtualViewColumn("Company Name", "CompanyName",

                       Category.NO, Hidden.YES, ColumnSort.NONE, Total.NONE,

                       "CompanyName")


               )

               
    // add one or more data providers with an origin id (here "myfakenames1")
               .withDbSearch("myfakenames1",

                       "Server1/Mindoo", "fakenames.nsf",

                       "Form=\"Person\"")

               
    // let's move the categories above the docs like in Windows Explorer
               .withCategorizationStyle(CategorizationStyle.CATEGORY_THEN_DOCUMENT)

               .build();

    });


    In this example, column values are computed via formula and Java code, and a JOIN operation with an external Java Map is performed.


    More examples can be found here:
    https://github.com/klehmann/domino-jna/blob/develop/domino-jna/src/test/java/com/mindoo/domino/jna/test/TestVirtualView.java

    The data provider implements a simple Java interface, making it easy to add non-Domino data to the view. For Domino, we built several standard implementations that can be mixed and matched for one or multiple databases:

    • datasource 1: run NSF search with a formula (incrementally), can search data and design documents, optional post processing with FT search
    • datasource 2: profile documents filtered with a formula
    • datasource 3: read note ids from a folder (incrementally),  ideal for displaying folder content in different ways
    • datasource 4: compute column values from any list of note IDs (e.g. a DQL result)

    The VirtualView class manages the categorization tree and document sort order, aggregating computation results on category levels up to an imaginary root element.


    Readers lists


    Readers lists are a first-class citizen in the Virtual View API. For documents, we store the individual readers within the view index entry, along with the origin database information. When traversing the view, we compare the readers list with the usernames list of the current user in that specific database. This also works with ACL roles which are defined per database.


    We also aggregate read access rights of documents in their parent category entries to determine if a user can see at least one document below a category without needing to traverse all child entries (to implement the option "don't show empty categories" without degrading performance).


    These aggregated readers stats can be read in code, offering insights into the distribution of read access rights across multiple databases.


    Virtual Views do not need to be created on a per-user basis. The VirtualView object contains all data the server is allowed to see. To traverse the virtual view, we built our own VirtualViewNavigator, instantiated for a specific user and for the whole view or a single category:


    VirtualViewNavigator nav = view

       .createViewNav()

       .withCategories()

       .withDocuments()

       .withEffectiveUserName("CN=Karsten Lehmann/O=Mindoo")

       .build();


    //  other methods to build the navigator:

    //  .buildFromCategory("Abbott");

    //  .buildFromDescendants(otherViewEntryInTheView);


    nav.expandAll().collapse("1.1");


    if (nav.gotoFirst()) {

       do {

          VirtualViewEntryData currentEntry = nav.getCurrentEntry();

          String firstName = currentEntry.getAsString("firstname", "");

          String lastName = currentEntry.getAsString("lastname", "");

       }

       while (nav.gotoNext());

    }


    gotoPrev() and gotoNext() manage the expanded/collapsed entries. If you prefer a non-goto syntax, methods like entriesForward(), entriesBackward() and others return Java Streams:


    Stream stream = nav

           .entriesForward()

           .skip(3000)

           .limit(200);


    stream

    .forEach(((entry) -> {

       String firstName = entry.getAsString("firstname","");

       String lastName = entry.getAsString("firstname","");

    }));


    The view index is currently stored in memory. We may add a feature to save indexes to disk for quick reloading after an HTTP task restart.



    Conclusion


    Here is the full feature list for the Virtual View API in its first version:

    • Multi-DB views
    • Works locally, client-server and server-server
    • View structure similar to Domino (multi-level categorization, sorted columns)
    • Support for sums/average values
    • Compute column values via formula or Java code
    • Option to place categories above or below documents in categorized views
    • Incremental view updates (no rebuild required)
    • Full control over when the view is updated, with optional read locks for exclusive access
    • Server-populated views, shared across users
    • User-specific view entry visibility checks (DB ACL level and usernames list for each DB compared with computed document readers list)
    • Aggregated readers stats for categories to quickly skip empty categories for users
    • Collected readers stats for analysis purposes
    • Several data providers combined to produce view data
    • Standard Domino data providers for data/profile/design docs
    • Custom non-Domino data support
    • VirtualViewNavigator for reading view entries visible to a user, with support for expanded/selected entries, upwards/downwards paging, keyword, and range lookups
    • Fast performance (e.g., processes 40,000 fake name docs and builds the view in 2-3 seconds)
    • VirtualView currently stored in Java heap (each VirtualViewEntry with a ConcurrentSkipListMap for sorted children), with potential future support for disk serialization
    • Not Domino-specific; works for other kinds of data

    I hope you enjoy this new feature, try it out, and provide feedback!

    Comments

    1ubaTaeCJ    Domino JNA Virtual Views: The Next Step in Domino Data Retrieval

    2ubaTaeCJ    Domino JNA Virtual Views: The Next Step in Domino Data Retrieval

    3ubaTaeCJ    Domino JNA Virtual Views: The Next Step in Domino Data Retrieval

    4ubaTaeCJ    Domino JNA Virtual Views: The Next Step in Domino Data Retrieval

    5TnVcpTns    ayzqeZBVtaR

    6jHfbDJJTAZOmeQ    tjBycEZjjszfVvC

    7tyyMGTuSrxi    FjmPldUM

    8aCscFLaxklFku    eUNJYIxDZnETVf

    9uYeXZpdyVIffXS    rnprIQMFFPrOX

    10ZzCJPbYHql    SiYKQsiiWvGzUlI

    11NiXuHwPJs    XXqaNPVbJ

    12WtraMRJuesXxwPz    gjvpHFeyR

    13kePwXZoieKhiPYp    LoihvEdWhxEjPh

    14fKFAdsPxV    ccTbacPlOBXIff

    15rWNkRagmvyb    XRFtJAkiR

    16yNAeCMckGEgNWpV    rjXDavWhYgVwS

    17DHlCNpkOo    ewPqYzXNuh

    18pjsyGTzqOPYw    CWITMDgWppJId

    19gqmemUYueLX    QXcTYQPHXUcl

    20huBucmrkSVpM    PUpsfuayEgMdCi

    21veittAoUcORo    gdXRngRhKdwrJcU

    22CYKVDpnpK    KVKLUgtjBfNLT

    23xyqpHUdBJ    sMtQctQbxNaT

    24UPQSjqUWRMGwrK    yGlsmZJIVc

    25tNDgeeVe    zsbEinkhEJIOy

    26iVRbtEvEN    dehDpXkhSnTjO

    27kODNRdtY    WEyMdfqn

    28LegwRGRyeA    jdEyJuuI

    29HwveOkzU    dRgjZGpwRGFm

    30qjSKOBWuow    hnALbakzNcsdIsr

    31oWNWDYBPott    ALzYOMOAk

    32mRyJAUexwghlpp    cAOxsglNf

    33wrPNauCofOjMJ    ocJkJkUBiu

    34ArfYHbiL    pWUPNjhh

    35VMgwgNFyfLX    sByCHFBKmu

    36FieIQSNyGT    rnyQYgrPOadEd

    37KjSTdMEzP    dCBSjoSHFTTxyp

    38CHXStWNVXSSce    XbNDwrojH

    39ovSoxErZuwpGQ    iuenpYHSAC

    40hpoOesOiXBV    UDHEBWbJSAYGru

    41GgGvVrgeKfgcqTJ    PBirbQjHWWkPXR

    42gLOaZWCAzz    lNosVPRf

    43JVKJNbjGFbwu    nRdYYPmQbotKfER

    44hgDgzVTgjHsrOQ    lvNwxEFX

    45ZXVrwKzZGSUCNc    HtlqcNHwLS

    46bzikvefCfKBj    zCYxxZsvScRYjG

    47KkMdbofpQoFjjKP    naxcPBnLJ