Query notifier

The Query<T> notifier provides notification for queries initiated by an application using the KeyQuery<T> class. The methods in this notifier are called on all nodes that are part of a distributed query on which Query<T> notifiers are installed. If a notifier is not installed on a node participating in a distributed query, the query succeeds without calling a notifier on that node.

Query notifier methods are always called, even if an application query would have been satisified by managed objcts in memory. The query notifier methods are responsible for determining whether a query should be passed to a secondary store or not based on the current contents of memory.

These methods are executed synchronously before the KeyQuery<T> API calls return to the application. The methods in this notifier control the query result set by creating and deleting managed objects as required.

The methods supported are:

Query<T> notifiers are installed for a specific key. They must be installed on the type that defines the key. They are inherited by all children of the type on which they are installed. The klass parameter to the methods can be used to determine the actual type on which the query is being performed.

The queryData parameter provides details on the application specified query information. It is used to reconstruct the query in the notifier.

The application requested LockMode is in the lockMode parameter. The notifier should attempt to honor the requested lockMode, but there is no enforcement.

Result set ordering is the responsibility of the TIBCO BusinessEvents® Extreme runtime, not the query notifiers. Any ordering specification on the application query is not available to the query notifiers as that information is not needed.

The Example 10.5, “Query notifier” shows how the application specified query is reconstructed in the query notifier methods.

Example 10.5. Query notifier

//     $Revision: 1.1.2.4 $
package com.kabira.snippets.store;

import com.kabira.platform.KeyComparisonOperator;
import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyFieldValueRangeList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyOrderedBy;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.Transaction;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;
import com.kabira.store.KeyField;
import com.kabira.store.Query;
import java.util.ArrayList;

/**
 * Secondary store query notifier
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class QueryNotifier
{
    @Managed
    @Key(name = "ByName", unique = true, ordered = true, fields =
    {
        "m_name"
    })
    private static class A
    {
        private A(final String name)
        {
            m_name = name;
        }

        private final String m_name;
    };

    private static class B extends A
    {
        public B(final String name)
        {
            super(name);
        }
    };

    //
    //  Query notifier
    //
    private static class Notifier extends Query<A>
    {
        public Notifier(final String keyName)
        {
            super(A.class, keyName);
        }

        @Override
        public void query(
            Class<? extends A> klass,
            LockMode lockMode,
            ArrayList<KeyField> queryData)
        {
            System.out.println("INFO:\t" + prettyPrintQuery(
                "object",
                klass.getSimpleName(),
                getKeyName(),
                lockMode,
                queryData));
        }

        @Override
        public void queryMinimum(
            Class<? extends A> klass,
            LockMode lockMode,
            ArrayList<KeyField> queryData)
        {
            System.out.println("INFO:\t" + prettyPrintQuery(
                "minimum",
                klass.getSimpleName(),
                getKeyName(),
                lockMode,
                queryData));
        }

        @Override
        public void queryMaximum(
            Class<? extends A> klass,
            LockMode lockMode,
            ArrayList<KeyField> queryData)
        {
            System.out.println("INFO:\t" + prettyPrintQuery(
                "maximum",
                klass.getSimpleName(),
                getKeyName(),
                lockMode,
                queryData));
        }
    }

	/**
	 * Main entry point
	 * @param args Not used
	 */
    public static void main(final String[] args)
    {
        initialize();
        uniqueQuery();
        minimumMaximumQuery();
        rangeQuery();
    }

    //
    //    Initialize notifier and create some test data
    //
    private static void initialize()
    {
        new Transaction("Initialize")
        {
            @Override
            protected void run() throws Transaction.Rollback
            {
                new Notifier("ByName");

                for (int i = 0; i < 20; i++)
                {
                    new A(Integer.toString(i));
                }
                for (int i = 20; i < 30; i++)
                {
                    new B(Integer.toString(i));
                }
            }
        }.execute();
    }

    //
    //    Perform a unique query
    //
    private static void uniqueQuery()
    {
        new Transaction("Unique Query")
        {
            @Override
            protected void run() throws Transaction.Rollback
            {
                KeyManager<B> km = new KeyManager<>();
                KeyQuery<B> kq = km.createKeyQuery(B.class, "ByName");
                KeyFieldValueList values = new KeyFieldValueList();
                values.add("m_name", "22");
                kq.defineQuery(values);

                System.out.println("INFO: getSingleResult " + kq);
                B b = kq.getSingleResult(LockMode.NOLOCK);
            }
        }.execute();
    }

    //
    //    Perform a range query
    //
    private static void rangeQuery()
    {
        new Transaction("Range Query")
        {
            @Override
            protected void run() throws Transaction.Rollback
            {
                KeyManager<B> km = new KeyManager<>();
                KeyQuery<B> kq = km.createKeyQuery(B.class, "ByName");
                KeyFieldValueRangeList values = new KeyFieldValueRangeList();
                values.add("m_name", "22", KeyComparisonOperator.GT);
                values.add("m_name", "28", KeyComparisonOperator.LTE);
                kq.defineQuery(values);

                System.out.println("INFO: Range Query " + kq);

                for (B b : kq.getResults(KeyOrderedBy.ASCENDING, LockMode.NOLOCK))
                {
                    break;
                }
            }
        }.execute();
    }

    //
    //    Perform minimum and maximum queries
    //
    private static void minimumMaximumQuery()
    {
        new Transaction("Minimum/Maximum Query")
        {
            @Override
            protected void run() throws Transaction.Rollback
            {
                KeyManager<A> km = new KeyManager<>();
                KeyQuery<A> kq = km.createKeyQuery(A.class, "ByName");

                System.out.println("INFO: Get Maximum Result");

                A a = kq.getMaximumResult(LockMode.READLOCK);

                KeyFieldValueRangeList values = new KeyFieldValueRangeList();
                values.add("m_name", "8", KeyComparisonOperator.GTE);
                kq.defineQuery(values);

                System.out.println("INFO: Get Minimum Result " + kq);
                a = kq.getMinimumResult(LockMode.NOLOCK);
            }
        }.execute();
    }

    //
    //  Pretty print query
    //
    private static String prettyPrintQuery(
        final String queryType,
        final String className,
        final String keyName,
        LockMode lockMode,
        ArrayList<KeyField> queryData)
    {
        StringBuilder builder = new StringBuilder();

        builder.append("select ");
        builder.append(queryType);
        builder.append(" from ");
        builder.append(className);
        builder.append(" using ");
        builder.append(keyName);
        builder.append(prettyPrintQueryData(queryData));
        builder.append(" with ");
        builder.append(lockMode);

        return builder.toString();
    }

    //
    //  Pretty print query data
    //
    private static String prettyPrintQueryData(final ArrayList<KeyField> queryData)
    {
        if (queryData.isEmpty() == true)
        {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        boolean first = true;

        builder.append(" where (");

        for (KeyField f : queryData)
        {
            if (first == true)
            {
                first = false;
            }
            else
            {
                builder.append(" && ");
            }
            builder.append(f.name);
            builder.append(" ");
            builder.append(f.comparisonOperator);
            builder.append(" ");
            builder.append(f.value);
        }

        builder.append(")");

        return builder.toString();
    }
}


When Example 10.5, “Query notifier” is run it outputs these messages (annotations added):

#
# Unique query on managed object B
#
INFO: getSingleResult select obj from com.kabira.snippets.store.QueryNotifier$B using ByName where (m_name == 22); Java constructor: (none)
INFO:    select object from B using ByName where (m_name == 22) with NOLOCK

#
# Maximum result query on managed object A
#
INFO: Get Maximum Result
INFO:    select maximum from A using ByName with READLOCK

#
# Minimum result query with a start range on managed object A
#
INFO: Get Minimum Result for obj in com.kabira.snippets.store.QueryNotifier$A using ByName where (m_name >= 8) { }
INFO:    select minimum from A using ByName where (m_name >= 8) with NOLOCK

#
# Range query on managed object B
#
INFO: Range Query for obj in com.kabira.snippets.store.QueryNotifier$B using ByName where (m_name > 22 && m_name <= 28) { }
INFO:    select object from B using ByName where (m_name > 22 && m_name <= 28) with NOLOCK