Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a caching lookup system in java for xpages

Tags:

xpages

We have a number of lookup databases that we use in multiple applications and I'm trying to figure out the best and most efficient way to make these lookup databases available via a java function or bean in an OSGi Plugin library.

What I'd like to achieve is some way of creating a function that I can pass in a lookup key and a field name and the function will return the correct value and possibly object type ( to deal with datatime values ). It would also need to cache the value for about an hour at the application level as these lookup documents do not change at all.

Typically I'd want to use them for display purposes so that I only need to store the key in my notes document and then use something like the following to display on screen what I need

<xp:text escape="true" id="computedField1">
    <xp:this.value><![CDATA[#{javascript:com.mycompany.lookup.GetDoc("docID","fieldName")}]]></xp:this.value>
</xp:text>
like image 252
Declan Lynch Avatar asked Mar 06 '12 15:03

Declan Lynch


People also ask

How do I create a cache in Java?

To create a cache, we can simply use a map / dictionary data structure and we can get the expected result of O(1) for both get and put operation. But, we can't store everything in our cache. We have storage and performance limits.

What is caching and how can you implement your cache in Java?

The Java Object Cache provides caching for expensive or frequently used Java objects when the application servers use a Java program to supply their content. Cached Java objects can contain generated pages or can provide support objects within the program to assist in creating new content.


1 Answers

You can pretty well do this now with scoped beans, with the one caveat that a bean is NSF specific. Although the XSP Starter kit I believe includes an example of how to do a Server scoped bean (which is really a singleton, meaning there is only one instance of the class for the entire JVM).

First create a simple serializable POJO called CachedData that has two member fields, the first is a field that holds a date time value that indicates when you last read the data from the disk, and the second is some sort of list object, like a vector, that holds your values.

Then create another POJO called ServerMap that has a map<string, map<string, map<string, map<Object, map<object, CachedData>>>> as a member, and a function called doCachedLookup() or something like that. The parameters to that function can be almost the same as an @DbLookup, server, database, view, key, etc. Then in the doCachedLookup, check your ServerMap for the existence of the specified server as the key. If it doesn't exist, then create a new map and insert it into the ServerMap with the key being the server name. If it does exist, then look up the database name in that map, then the view in the next map, then finally the value in the last map. Once you get the CachedData object, you can check the date time field and see if it is expired, if it isn't, return the vector, and if it is, discard it, and then do a fresh lookup, and re-cache the data, and then return the vector.

Here's the code samples, i was a bit lazy in my overloaded methods for getting a column versus getting a field name, and i use some deprecated java date methods, but it will give you a good base to start with. All code is tested:

CachedData Class:

package com.ZetaOne.example;

import java.io.Serializable;
import java.util.Date;
import java.util.Vector;

public class CachedData implements Serializable {

    private static final long serialVersionUID = 1L;
    private Date updateTime;
    private Vector<Object> values;

    public Date getUpdateTime() {
        return this.updateTime;
    }

    public void setUpdateTime(Date UpdateTime) {
        updateTime = UpdateTime;
    }

    public Vector<Object> getValues() {
        return this.values;
    }

    public void setValues(Vector<Object> values) {
        this.values = values;
    }
}

CachedLookup class that is implemented as a singleton so that it can be used server-wide:

package com.ZetaOne.example;

import java.io.Serializable;
import java.util.Date;
import java.util.Vector;
import com.ZetaOne.example.CachedData;
import java.util.HashMap;
import java.util.Collections;
import java.util.Map;

import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewEntryCollection;
import lotus.domino.ViewEntry;
import lotus.domino.Document;

import javax.faces.context.FacesContext;

public class CachedLookup implements Serializable {

    private static CachedLookup _instance;

    private static final long serialVersionUID = 1L;
    private Map<String, HashMap<String, HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>>> cachedLookup;

    public static CachedLookup getCurrentInstance() {
        if (_instance == null) {
            _instance = new CachedLookup();
        }
        return _instance;
    }       

    private CachedLookup() {
        HashMap<String, HashMap<String, HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>>> cachedLookupMap =
            new HashMap<String, HashMap<String, HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>>>();
        this.cachedLookup = Collections.synchronizedMap(cachedLookupMap);
    }

    @SuppressWarnings("deprecation")
    public Vector<Object> doCachedLookup(String serverName, String filePath, String viewName, Object keyValues, int columnNumber, boolean exactMatch) {

        if (cachedLookup.containsKey(serverName)) {
            if (cachedLookup.get(serverName).containsKey(filePath)) {
                if (cachedLookup.get(serverName).get(filePath).containsKey(viewName)) {
                    if (cachedLookup.get(serverName).get(filePath).get(viewName).containsKey(keyValues)) {
                        if (cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).containsKey(columnNumber)) {
                            CachedData cache = cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).get(columnNumber);
                            if (cache.getUpdateTime().compareTo(new Date()) > 0) {
                                System.out.println("Cache Hit");
                                return cache.getValues();
                            }
                        }
                    }
                }
            }
        }

        System.out.println("Cache Miss");
        // if we drop to here, cache is either expired or not present, do the lookup.

        try {
            Session session = (Session)resolveVariable("session");
            Database db = session.getDatabase(serverName, filePath);
            View view = db.getView(viewName);
            ViewEntryCollection vc = view.getAllEntriesByKey(keyValues, exactMatch);
            ViewEntry ve, vn;
            ve = vc.getFirstEntry();
            Vector<Object> results = new Vector<Object>();
            while (ve != null) {
                results.add(ve.getColumnValues().elementAt(columnNumber));

                vn = vc.getNextEntry();
                ve.recycle();
                ve = vn;
            }

            vc.recycle();

            if (!cachedLookup.containsKey(serverName)) {
                cachedLookup.put(serverName, new HashMap<String, HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>>());
            }

            if (!cachedLookup.get(serverName).containsKey(filePath)) {
                cachedLookup.get(serverName).put(filePath, new HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>());
            }

            if (!cachedLookup.get(serverName).get(filePath).containsKey(viewName)) {
                cachedLookup.get(serverName).get(filePath).put(viewName, new HashMap<Object, HashMap<Object, CachedData>>());
            }

            if (!cachedLookup.get(serverName).get(filePath).get(viewName).containsKey(keyValues)) {
                cachedLookup.get(serverName).get(filePath).get(viewName).put(keyValues, new HashMap<Object, CachedData>());
            }

            CachedData cache;
            if (cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).containsKey(columnNumber)) {
                cache = cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).get(columnNumber);  
            } else {
                cache = new CachedData();
            }

            Date dt = new Date();
            dt.setHours(dt.getHours() + 1);
            cache.setUpdateTime(dt);
            cache.setValues(results);           

            cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).put(columnNumber, cache);

            view.recycle();
            db.recycle();

            return results;

        } catch (NotesException e) {
            // debug here, im lazy
            return null;
        }
    }

    public Vector<Object> doCachedLookup(String serverName, String filePath, String viewName, Object keyValues, String fieldName, boolean exactMatch) {

        if (cachedLookup.containsKey(serverName)) {
            if (cachedLookup.get(serverName).containsKey(filePath)) {
                if (cachedLookup.get(serverName).get(filePath).containsKey(viewName)) {
                    if (cachedLookup.get(serverName).get(filePath).get(viewName).containsKey(keyValues)) {
                        if (cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).containsKey(fieldName)) {
                            CachedData cache = cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).get(fieldName);
                            if (cache.getUpdateTime().compareTo(new Date()) > 0) {
                                System.out.println("Cache Hit");                                
                                return cache.getValues();
                            }
                        }
                    }
                }
            }
        }

        System.out.println("Cache Miss");           
        // if we drop to here, cache is either expired or not present, do the lookup.

        try {
            Session session = (Session)resolveVariable("session");
            Database db = session.getDatabase(serverName, filePath);
            View view = db.getView(viewName);
            ViewEntryCollection vc = view.getAllEntriesByKey(keyValues, exactMatch);
            ViewEntry ve, vn;
            ve = vc.getFirstEntry();
            Vector<Object> results = new Vector<Object>();
            while (ve != null) {
                Document doc = ve.getDocument();
                results.add(doc.getItemValue(fieldName));
                doc.recycle();

                vn = vc.getNextEntry();
                ve.recycle();
                ve = vn;
            }

            vc.recycle();

            if (!cachedLookup.containsKey(serverName)) {
                cachedLookup.put(serverName, new HashMap<String, HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>>());
            }

            if (!cachedLookup.get(serverName).containsKey(filePath)) {
                cachedLookup.get(serverName).put(filePath, new HashMap<String, HashMap<Object, HashMap<Object, CachedData>>>());
            }

            if (!cachedLookup.get(serverName).get(filePath).containsKey(viewName)) {
                cachedLookup.get(serverName).get(filePath).put(viewName, new HashMap<Object, HashMap<Object, CachedData>>());
            }

            if (!cachedLookup.get(serverName).get(filePath).get(viewName).containsKey(keyValues)) {
                cachedLookup.get(serverName).get(filePath).get(viewName).put(keyValues, new HashMap<Object, CachedData>());
            }

            CachedData cache;
            if (cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).containsKey(fieldName)) {
                cache = cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).get(fieldName); 
            } else {
                cache = new CachedData();
            }

            Date dt = new Date();
            dt.setHours(dt.getHours() + 1);
            cache.setUpdateTime(dt);
            cache.setValues(results);           

            cachedLookup.get(serverName).get(filePath).get(viewName).get(keyValues).put(fieldName, cache);

            view.recycle();
            db.recycle();

            return results;

        } catch (NotesException e) {
            // debug here, im lazy
            return null;
        }
    }   

    private static Object resolveVariable(String variable) {
        return FacesContext.getCurrentInstance().getApplication()
                .getVariableResolver().resolveVariable(
                        FacesContext.getCurrentInstance(), variable);
    }   

}

Example on how to use in an XPage:

<xp:text id="text1">
    <xp:this.value><![CDATA[#{javascript:
        com.ZetaOne.example.CachedLookup.getCurrentInstance().doCachedLookup(
            database.getServer(),
            database.getFilePath(),
            "lookup",
            "Test Category",
            "Value",
            true
        )
    }]]></xp:this.value>
</xp:text>

like image 129
Jeremy Hodge Avatar answered Dec 06 '22 05:12

Jeremy Hodge