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.2.1 $ $Date: 2015/10/08 22:36:03 $
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 keyDesc1,
261            final ManagedObjectInputStream keyStream1,
262            final int rop1,
263            final ManagedObjectStreamClass keyDesc2,
264            final ManagedObjectInputStream keyStream2,
265            final int rop2,
266            final ManagedObjectStreamClass keyDesc,
267            final KeyFilter[] filters)
268            throws IOException, ClassNotFoundException
269    {
270        LockMode lockMode = mapLockMode(objSrvLockMode);
271        ArrayList<KeyField> queryData = buildQueryData(
272                keyDesc1, keyStream1, rop1, keyDesc2,
273                keyStream2, rop2, keyDesc, filters);
274
275        if (rangeQueryType == RangeQueryType_RangeQuery)
276        {
277            query(klass, lockMode, queryData);
278        }
279        else if (rangeQueryType == RangeQueryType_MinimumQuery)
280        {
281            queryMinimum(klass, lockMode, queryData);
282        }
283        else
284        {
285            assert rangeQueryType == RangeQueryType_MaximumQuery
286                : rangeQueryType + " != " + RangeQueryType_MaximumQuery;
287            queryMaximum(klass, lockMode, queryData);
288        }
289    }
290
291    private static void audit(final Class<?> klass, final String keyName)
292    {
293        if (! ManagedObject.isManagedClass(klass))
294        {
295            throw new ManagedClassError(
296                    klass.getName() + " is not @Managed");
297        }
298
299        for (Annotation annotation :  klass.getDeclaredAnnotations())
300        {
301            Class<?> annotationClass = annotation.annotationType();
302
303            if (annotationClass == Key.class)
304            {
305                if (((Key)annotation).name().equals(keyName))
306                {
307                    return;
308                }
309            }
310            else if (annotationClass == KeyList.class)
311            {
312                KeyList keyList = (KeyList)annotation;
313
314                for (Key key : keyList.keys())
315                {
316                    if (key.name().equals(keyName))
317                    {
318                        return;
319                    }
320                }
321            }
322        }
323
324        throw new KeyUnknownKeyNameError(
325                "Key " + keyName + " is not defined on "
326                + "Managed class " + klass.getName());
327    }
328
329    private LockMode mapLockMode(final int objSrvLockMode)
330    {
331        switch (objSrvLockMode)
332        {
333            case KeyOptNoLock:
334                return LockMode.NOLOCK;
335            case KeyOptReadLock:
336                return LockMode.READLOCK;
337        }
338
339        assert objSrvLockMode == KeyOptWriteLock;
340
341        return LockMode.WRITELOCK;
342    }
343
344    static KeyComparisonOperator mapComparisonOp(final int rop)
345    {
346        switch (rop)
347        {
348            case ObjSrv_EQ:
349                return KeyComparisonOperator.EQ;
350            case ObjSrv_GT:
351                return KeyComparisonOperator.GT;
352            case ObjSrv_GTE:
353                return KeyComparisonOperator.GTE;
354            case ObjSrv_LT:
355                return KeyComparisonOperator.LT;
356            case ObjSrv_LTE:
357                return KeyComparisonOperator.LTE;
358        }
359
360        assert rop == ObjSrv_NEQ;
361        return KeyComparisonOperator.NEQ;
362    }
363
364    // filter ops are the opposite of comparison ops
365    static KeyComparisonOperator mapFilterOp(final int rop)
366    {
367        switch (rop)
368        {
369            case ObjSrv_EQ:
370                return KeyComparisonOperator.NEQ;
371            case ObjSrv_GT:
372                return KeyComparisonOperator.LTE;
373            case ObjSrv_GTE:
374                return KeyComparisonOperator.LT;
375            case ObjSrv_LT:
376                return KeyComparisonOperator.GTE;
377            case ObjSrv_LTE:
378                return KeyComparisonOperator.GT;
379        }
380
381        assert rop == ObjSrv_NEQ;
382        return KeyComparisonOperator.EQ;
383    }
384
385    private ArrayList<KeyField> buildQueryData(
386            final ManagedObjectStreamClass keyDescription,
387            final ManagedObjectInputStream keyStream)
388            throws IOException, ClassNotFoundException
389    {
390        ArrayList<KeyField> queryData = new ArrayList<KeyField>();
391
392        ObjectInputStream.GetField gf = keyStream.readFields();
393
394        for (ObjectStreamField field : keyDescription.getFields())
395        {
396            final String fieldName = field.getName();
397            final Object value = gf.get(fieldName, null);
398            final KeyField keyField =
399                    new KeyField(fieldName, value, KeyComparisonOperator.EQ);
400
401            queryData.add(keyField);
402        }
403
404        return queryData;
405    }
406
407    private ArrayList<KeyField> buildQueryData(
408            final ManagedObjectStreamClass keyDescription1,
409            final ManagedObjectInputStream keyStream1,
410            final int rop1,
411            final ManagedObjectStreamClass keyDescription2,
412            final ManagedObjectInputStream keyStream2,
413            final int rop2,
414            final ManagedObjectStreamClass keyDescription,
415            final KeyFilter[] filters)
416            throws IOException, ClassNotFoundException
417    {
418        ArrayList<KeyField> queryData = new ArrayList<KeyField>();
419        ObjectInputStream.GetField gf;
420        KeyComparisonOperator rop;
421
422        if (keyStream1 != null)
423        {
424            gf = keyStream1.readFields();
425            rop = mapComparisonOp(rop1);
426
427            for (ObjectStreamField field : keyDescription1.getFields())
428            {
429                final String fieldName = field.getName();
430                queryData.add(new KeyField(
431                    fieldName, gf.get(fieldName, null), rop));
432            }
433        }
434
435        if (keyStream2 != null)
436        {
437            gf = keyStream2.readFields();
438            rop = mapComparisonOp(rop2);
439
440            for (ObjectStreamField field : keyDescription2.getFields())
441            {
442                final String fieldName = field.getName();
443                queryData.add(new KeyField(
444                    fieldName, gf.get(fieldName, null), rop));
445            }
446        }
447
448        if (filters != null)
449        {
450            for (KeyFilter filter : filters)
451            {
452                gf = filter.m_keyData.readFields();
453
454                int keyDepth = filter.m_keyDepth;
455
456                final ObjectStreamField[] fields = keyDescription.getFields();
457
458                assert keyDepth < fields.length;
459                final ObjectStreamField field = fields[keyDepth];
460
461                final String fieldName = field.getName();
462                queryData.add(new KeyField(
463                        fieldName, gf.get(fieldName, null), filter.m_rop));
464            }
465        }
466
467        return queryData;
468    }
469
470    static
471    {
472        Runtime.getRuntime().addShutdownHook(new Thread()
473        {
474            @Override
475            public void run()
476            {
477                new Transaction("Query notifier shutdown")
478                {
479                    @Override
480                    protected void run()
481                    {
482                        NotifierHash.clearNotifiers();
483                    }
484                }.execute();
485            }
486        });
487    }
488
489    private static class NotifierHash
490    {
491        static final HashSet<Query<?>> m_installedNotifiers
492                = new HashSet<Query<?>>();
493
494        static void addToHash(final Query<?> notifier)
495        {
496            m_installedNotifiers.add(notifier);
497        }
498
499        static void clearNotifiers()
500        {
501            for (Query<?> query : m_installedNotifiers)
502            {
503                if (! ManagedObject.isEmpty(query))
504                {
505                    ManagedObject.delete(query);
506                }
507            }
508        }
509    }
510}