001//
002// Name
003//  $RCSfile: Query.java,v $
004//
005// Copyright
006//  Copyright 2014 Cloud Software Group, Inc. ALL RIGHTS RESERVED.
007//  Cloud Software Group, Inc. Confidential Information
008//
009// History
010//  $Revision: 1.1.2.9 $ $Date: 2014/11/26 22:22:12 $
011//
012package com.kabira.store;
013
014import com.kabira.platform.*;
015import com.kabira.platform.annotation.Key;
016import com.kabira.platform.annotation.KeyList;
017import com.kabira.platform.annotation.Managed;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectStreamField;
022import java.lang.annotation.Annotation;
023import java.util.ArrayList;
024import java.util.HashSet;
025
026/**
027 * Class for receiving notifications of
028 * {@link com.kabira.platform.KeyQuery} results calls.
029 * <p>
030 * A <code>Query</code> notifier does not directly return results.
031 * The notification provides an opportunity to change the contents of
032 * shared memory (creating and or deleting Managed objects) before
033 * a keyed query is done.
034 * For example, it could be used for creating Managed objects from data
035 * in a database.
036 */
037@Managed
038public abstract class Query<T> extends QueryBase
039{
040    private static final int RangeQueryType_RangeQuery = 1;
041    private static final int RangeQueryType_MinimumQuery = 2;
042    private static final int RangeQueryType_MaximumQuery = 3;
043
044    // FIX THIS, DS: these are a copy from KeyQuery.java
045    //               but I don't want to make them public there.
046    //
047    // enumeration values for ObjSrv::RangeOperator
048    static final int ObjSrv_EQ       = 1;
049    static final int ObjSrv_NEQ      = 2;
050    static final int ObjSrv_GTE      = 3;
051    static final int ObjSrv_GT       = 4;
052    static final int ObjSrv_LTE      = 5;
053    static final int ObjSrv_LT       = 6;
054    // enumeration values for OSKeyOpt::ObjectLock
055    static final int KeyOptReadLock  = 1;
056    static final int KeyOptWriteLock = 2;
057    static final int KeyOptNoLock    = 3;
058
059    // force usage of protected ctor
060    private Query()
061    {
062    }
063
064    /** Constructor.
065     * <p>
066     * Creating an instance automatically installs it
067     * as the query notifier for the associated Managed class and key.
068     * <p>
069     * A notifier may only be set on the class where the key is defined.
070     * <p>
071     * The notifier will apply to queries on the class where the key is
072     * defined, and queries on classes which inherit the key.
073     * <p>
074     * If a notifier is already installed for the key,
075     * the new instance replaces the previous instance.
076     * <p>
077     * Deleting a notifier automatically uninstalls it.
078     * <p>
079     * Notifiers are automatically deleted when the JVM shuts down.
080     * Notifiers created in other JVMs are not affected.
081     * <p>
082     * When a {@link KeyQuery} results method is called, the Query notifier
083     * for the key will be called before executing the query.
084     *
085     * @param klass The Managed class where
086     *              <code>keyName</code> is defined.
087     * @param keyName      The key to receive query notifications for.
088     *
089     * @throws ManagedClassError    If <code>klass</code> is not managed.
090     *
091     * @throws KeyUnknownKeyNameError if <code>keyName</code> is not
092     *         defined on <code>klass</code>.
093     */
094    protected Query(final Class<T> klass, final String keyName)
095        throws ManagedClassError, KeyUnknownKeyNameError
096    {
097        audit(klass, keyName);
098
099        m_className = klass.getName();
100        m_keyName = keyName;
101        install();
102        NotifierHash.addToHash(this);
103    }
104
105    /**
106     * Return the currently installed Query notifier for
107     * the Managed class and key.
108     * <p>
109     * Inheritance is not examined by this method.
110     *
111     * @param klass  Class where key is defined.
112     * @param key    Name of key.
113     * @return Query or null.
114     * @throws ManagedClassError If <code>klass</code> is not managed.
115     * @throws KeyUnknownKeyNameError If <code>keyName</code> is not
116     *         defined on <code>klass</code>.
117     */
118    public static Query<?> getNotifier(
119            final Class<?> klass, final String key)
120    {
121        audit(klass, key);
122        return (Query)StoreUtil.getQueryNotifier(klass.getName(), key);
123    }
124
125    /**
126     * Get the name of the key for this Query notifier.
127     * @return Key name.
128     */
129    public final String getKeyName()
130    {
131        return m_keyName;
132    }
133
134    /**
135     * Notifier method for {@link KeyQuery} results calls.
136     * <p>
137     * Called by the system when following KeyQuery methods are called,
138     * but before they are executed:
139     * <p>
140     * {@link KeyQuery#getOrCreateSingleResult} - for unique keys
141     * <p>
142     * {@link KeyQuery#getSingleResult} - for unique keys
143     * <p>
144     * {@link KeyQuery#getResults} - for non unique keys
145     * <p>
146     * {@link com.kabira.platform.KeyOrderedBy} does not need to be
147     * considered by the notifier.
148     * Result set ordering is handled by the runtime when the
149     * query executes.
150     * <p>
151     * Will be called on each of the nodes in the
152     * {@link com.kabira.platform.QueryScope} of the KeyQuery results call.
153     * <p>
154     * Work done within Query.query() will not
155     * generate calls to com.kabira.store notifiers on the local node.
156     * <p>
157     * <b>Note:</b> work done within the Query.query() method will generate
158     * calls to com.kabira.store notifiers on remote nodes.
159     *
160     * @param klass    The Managed class for the query results call.
161     *                 This may be a subtype of the class
162     *                 associated with the notifier.
163     * @param lockMode The lock mode for the query results. The notifier
164     *                 implementation should attempt to honor this lock mode,
165     *                 but there is no enforcement.
166     * @param queryData The query data, ordered as the caller added it
167     *                  to the {@link com.kabira.platform.KeyFieldValueList}
168     *                  or {@link com.kabira.platform.KeyFieldValueRangeList}
169     *                  for the query.
170     *                  See {@link KeyField}.
171     */
172    public abstract void query(
173            final Class<? extends T> klass,
174            final LockMode lockMode,
175            final ArrayList<KeyField> queryData);
176
177    /**
178     * Notifier method for {@link KeyQuery#getMinimumResult} calls.
179     * <p>
180     * Called by the system when KeyQuery.getMinimumResult
181     * is called, but before it is executed.
182     * <p>
183     * Will be called on each of the nodes in the
184     * {@link com.kabira.platform.QueryScope} of getMinimumResult
185     * call.
186     * <p>
187     * Work done within Query.queryMinimum() will not
188     * generate calls to com.kabira.store notifiers on the local node.
189     * <p>
190     * <b>Note:</b> work done within Query.queryMinimum() will generate
191     * calls to com.kabira.store notifiers on remote nodes.
192     *
193     * @param klass    The Managed class for the getMinimumResult call.
194     *                 This may be a subtype of the class associated
195     *                 with the notifier.
196     * @param lockMode The lock mode for the query result. The notifier
197     *                 implementation should attempt to honor this lock mode,
198     *                 but there is no enforcement.
199     * @param queryData The query data, ordered as the caller added it
200     *                  to the {@link com.kabira.platform.KeyFieldValueList}
201     *                  or {@link com.kabira.platform.KeyFieldValueRangeList}
202     *                  for the query.
203     *                  See {@link KeyField}.
204     */
205    public abstract void queryMinimum(
206            final Class<? extends T> klass,
207            final LockMode lockMode,
208            final ArrayList<KeyField> queryData);
209
210    /**
211     * Notifier method for {@link KeyQuery#getMaximumResult} calls.
212     * <p>
213     * Called by the system when KeyQuery.getMaximumResult
214     * is called, but before it is executed.
215     * <p>
216     * Will be called on each of the nodes in the
217     * {@link com.kabira.platform.QueryScope} of getMaximumResult
218     * call.
219     * <p>
220     * Work done within Query.queryMaximum() will not
221     * generate calls to com.kabira.store notifiers on the local node.
222     * <p>
223     * <b>Note:</b> work done within Query.queryMaximum() will generate
224     * calls to com.kabira.store notifiers on remote nodes.
225     *
226     * @param klass    The Managed class for the getMaximumResult call.
227     *                 This may be a subtype of the class associated with
228     *                 the notifier.
229     * @param lockMode The lock mode for the query result. The notifier
230     *                 implementation should attempt to honor this lock mode,
231     *                 but there is no enforcement.
232     * @param queryData The query data, ordered as the caller added it
233     *                  to the {@link com.kabira.platform.KeyFieldValueList}
234     *                  or {@link com.kabira.platform.KeyFieldValueRangeList}
235     *                  for the query.
236     *                  See {@link KeyField}.
237     */
238    public abstract void queryMaximum(
239            final Class<? extends T> klass,
240            final LockMode lockMode,
241            final ArrayList<KeyField> queryData);
242
243    void querySimpleInternal(
244            final Class<? extends T> klass,
245            final int objSrvLockMode,
246            final ManagedObjectInputStream keyStream)
247            throws IOException, ClassNotFoundException
248    {
249        ManagedObjectStreamClass keyDesc = keyStream.getStreamClass();
250        LockMode lockMode = mapLockMode(objSrvLockMode);
251        ArrayList<KeyField> queryData = buildQueryData(keyDesc, keyStream);
252
253        query(klass, lockMode, queryData);
254    }
255
256    void queryOrderedInternal(
257            final Class<? extends T> klass,
258            final int objSrvLockMode,
259            final int rangeQueryType,
260            final ManagedObjectStreamClass keyDesc,
261            final ManagedObjectInputStream keyStream1,
262            final int rop1,
263            final ManagedObjectInputStream keyStream2,
264            final int rop2,
265            final KeyFilter[] filters)
266            throws IOException, ClassNotFoundException
267    {
268        LockMode lockMode = mapLockMode(objSrvLockMode);
269        ArrayList<KeyField> queryData = buildQueryData(
270                keyDesc, keyStream1, rop1, keyStream2, rop2, filters);
271
272        if (rangeQueryType == RangeQueryType_RangeQuery)
273        {
274            query(klass, lockMode, queryData);
275        }
276        else if (rangeQueryType == RangeQueryType_MinimumQuery)
277        {
278            queryMinimum(klass, lockMode, queryData);
279        }
280        else
281        {
282            assert rangeQueryType == RangeQueryType_MaximumQuery
283                : rangeQueryType + " != " + RangeQueryType_MaximumQuery;
284            queryMaximum(klass, lockMode, queryData);
285        }
286    }
287
288    private static void audit(final Class<?> klass, final String keyName)
289    {
290        if (! ManagedObject.isManagedClass(klass))
291        {
292            throw new ManagedClassError(
293                    klass.getName() + " is not @Managed");
294        }
295
296        for (Annotation annotation :  klass.getDeclaredAnnotations())
297        {
298            Class<?> annotationClass = annotation.annotationType();
299
300            if (annotationClass == Key.class)
301            {
302                if (((Key)annotation).name().equals(keyName))
303                {
304                    return;
305                }
306            }
307            else if (annotationClass == KeyList.class)
308            {
309                KeyList keyList = (KeyList)annotation;
310
311                for (Key key : keyList.keys())
312                {
313                    if (key.name().equals(keyName))
314                    {
315                        return;
316                    }
317                }
318            }
319        }
320
321        throw new KeyUnknownKeyNameError(
322                "Key " + keyName + " is not defined on "
323                + "Managed class " + klass.getName());
324    }
325
326    private LockMode mapLockMode(final int objSrvLockMode)
327    {
328        switch (objSrvLockMode)
329        {
330            case KeyOptNoLock:
331                return LockMode.NOLOCK;
332            case KeyOptReadLock:
333                return LockMode.READLOCK;
334        }
335
336        assert objSrvLockMode == KeyOptWriteLock;
337
338        return LockMode.WRITELOCK;
339    }
340
341    static KeyComparisonOperator mapComparisonOp(final int rop)
342    {
343        switch (rop)
344        {
345            case ObjSrv_EQ:
346                return KeyComparisonOperator.EQ;
347            case ObjSrv_GT:
348                return KeyComparisonOperator.GT;
349            case ObjSrv_GTE:
350                return KeyComparisonOperator.GTE;
351            case ObjSrv_LT:
352                return KeyComparisonOperator.LT;
353            case ObjSrv_LTE:
354                return KeyComparisonOperator.LTE;
355        }
356
357        assert rop == ObjSrv_NEQ;
358        return KeyComparisonOperator.NEQ;
359    }
360
361    // filter ops are the opposite of comparison ops
362    static KeyComparisonOperator mapFilterOp(final int rop)
363    {
364        switch (rop)
365        {
366            case ObjSrv_EQ:
367                return KeyComparisonOperator.NEQ;
368            case ObjSrv_GT:
369                return KeyComparisonOperator.LTE;
370            case ObjSrv_GTE:
371                return KeyComparisonOperator.LT;
372            case ObjSrv_LT:
373                return KeyComparisonOperator.GTE;
374            case ObjSrv_LTE:
375                return KeyComparisonOperator.GT;
376        }
377
378        assert rop == ObjSrv_NEQ;
379        return KeyComparisonOperator.EQ;
380    }
381
382    private ArrayList<KeyField> buildQueryData(
383            final ManagedObjectStreamClass keyDescription,
384            final ManagedObjectInputStream keyStream)
385            throws IOException, ClassNotFoundException
386    {
387        ArrayList<KeyField> queryData = new ArrayList<KeyField>();
388
389        ObjectInputStream.GetField gf = keyStream.readFields();
390
391        for (ObjectStreamField field : keyDescription.getFields())
392        {
393            final String fieldName = field.getName();
394            final Object value = gf.get(fieldName, null);
395            final KeyField keyField =
396                    new KeyField(fieldName, value, KeyComparisonOperator.EQ);
397
398            queryData.add(keyField);
399        }
400
401        return queryData;
402    }
403
404    private ArrayList<KeyField> buildQueryData(
405            final ManagedObjectStreamClass keyDescription,
406            final ManagedObjectInputStream keyStream1,
407            final int rop1,
408            final ManagedObjectInputStream keyStream2,
409            final int rop2,
410            final KeyFilter[] filters)
411            throws IOException, ClassNotFoundException
412    {
413        ArrayList<KeyField> queryData = new ArrayList<KeyField>();
414        ObjectInputStream.GetField gf;
415        KeyComparisonOperator rop;
416
417        if (keyStream1 != null)
418        {
419            gf = keyStream1.readFields();
420            rop = mapComparisonOp(rop1);
421
422            for (ObjectStreamField field : keyDescription.getFields())
423            {
424                final String fieldName = field.getName();
425                queryData.add(new KeyField(
426                    fieldName, gf.get(fieldName, null), rop));
427            }
428        }
429
430        if (keyStream2 != null)
431        {
432            gf = keyStream2.readFields();
433            rop = mapComparisonOp(rop2);
434
435            for (ObjectStreamField field : keyDescription.getFields())
436            {
437                final String fieldName = field.getName();
438                queryData.add(new KeyField(
439                    fieldName, gf.get(fieldName, null), rop));
440            }
441        }
442
443        if (filters != null)
444        {
445            for (KeyFilter filter : filters)
446            {
447                gf = filter.m_keyData.readFields();
448
449                for (ObjectStreamField field : keyDescription.getFields())
450                {
451                    final String fieldName = field.getName();
452                    queryData.add(new KeyField(
453                            fieldName, gf.get(fieldName, null), filter.m_rop));
454                }
455            }
456        }
457
458        return queryData;
459    }
460
461    static
462    {
463        Runtime.getRuntime().addShutdownHook(new Thread()
464        {
465            @Override
466            public void run()
467            {
468                new Transaction("Query notifier shutdown")
469                {
470                    @Override
471                    protected void run()
472                    {
473                        NotifierHash.clearNotifiers();
474                    }
475                }.execute();
476            }
477        });
478    }
479
480    private static class NotifierHash
481    {
482        static final HashSet<Query<?>> m_installedNotifiers
483                = new HashSet<Query<?>>();
484
485        static void addToHash(final Query<?> notifier)
486        {
487            m_installedNotifiers.add(notifier);
488        }
489
490        static void clearNotifiers()
491        {
492            for (Query<?> query : m_installedNotifiers)
493            {
494                if (! ManagedObject.isEmpty(query))
495                {
496                    ManagedObject.delete(query);
497                }
498            }
499        }
500    }
501
502}