001// 
002// Name
003//  $RCSfile: Transaction.java,v $
004// 
005// COPYRIGHT
006//  Copyright 2008-2017 Cloud Software Group, Inc. ALL RIGHTS RESERVED.
007//  Cloud Software Group, Inc. Confidential Information
008//
009// History
010//  $Revision: 1.1.2.31.2.2 $ $Date: 2017/01/26 17:43:13 $
011// 
012
013package com.kabira.platform;
014
015import com.kabira.platform.annotation.Managed;
016
017import java.io.StringWriter;
018import java.io.PrintWriter;
019
020/** 
021 * Executes code in the context of a transaction.
022 *
023 * <p> The class should be extended and the abstract {@link Transaction#run}
024 * method implemented. When executed via {@link Transaction#execute},
025 * all code executed within the <code>run</code> method will be
026 * transactional, changes to Managed objects are logged.
027 *
028 * <p> Object instances that are modified are write locked into the
029 * transaction, blocking access from other transactions until the
030 * transaction commits or rolls back.
031 *
032 * <p> If the default {@link IsolationLevel#SERIALIZABLE} Isolation level
033 * is used, object instances that are accessed but not modified will be
034 * read locked into the transaction. This will allow other transactions
035 * to also access and read lock the object, but prevents other
036 * transactions from modifying the object.
037 *
038 * <p> If the {@link IsolationLevel#READ_COMMITTED_SNAPSHOT} Isolation
039 * level is used, object instances that are accessed but not modified
040 * will have a snapshot of the data taken when first accessed in the
041 * transaction. All subseqeunt accesses to the object data within the
042 * same transaction will use this snapshot in order to present a
043 * consistent view of the data. This will allow other transactions to
044 * both access and modify the object. If a transaction takes a snapshot,
045 * and subsequently attempts to modify the object, the runtime will
046 * determine if the object data has been modified by another transaction
047 * since the snapshot was taken. If it has been modified, a deadlock
048 * exception is thrown in order to retry the transaction.
049 *
050 * <p> Any deadlocks that occur will automatically cause a roll back of the
051 * current transaction, and the code within the <code>run</code> method will
052 * be re-executed. This means that any code called within the
053 * <code>run</code> method must be transaction safe. If the work done within
054 * the <code>run</code> method should not be executed more than once, the work
055 * should be deferred until commit time by using a transaction commit
056 * trigger.
057 *
058 * <h3>Exception handling</h3>
059 *
060 * If the code within the <code>run</code> method throws a {@link
061 * Transaction.Rollback} exception with no cause, the
062 * <code>execute</code> call will roll back the transaction, and return
063 * {@link Transaction.Result#ROLLBACK} to the caller. In order to support
064 * exception propagation through the <code>execute</code> method, the
065 * following mechanisms can be used:
066 * <ul>
067 * <li> If an unchecked exception is thrown in the <code>run</code>
068 * method, the exception will be rethrown to the caller of <code>execute</code>.
069 * <li> If the implementor of the <code>run</code> method wishes to
070 * propagate a checked exception, they can throw a Rollback exception
071 * with a cause. When caught by <code>execute</code>, the Rollback
072 * exception cause and message will be wrapped in a
073 * {@link Transaction.InvocationRunException} and thrown back to the caller.
074 * </ul>
075 * In both cases, the transaction is rolled back before the exception is
076 * propagated. 
077 */
078public abstract class Transaction
079{
080    /**
081     *  Execute method return values.
082     */
083    public enum Result
084    {
085        /** Transaction committed. */
086        COMMIT,
087
088        /** Transaction rolled back. */
089        ROLLBACK
090    }
091
092    /**
093     *  Transaction isolation levels.
094     */
095    public enum IsolationLevel
096    {
097        /**
098         * Access data that has been committed. A snapshot of the data is
099         * taken when a field is read, but locks are immediately released
100         * to allow other transactions to update the data. Multiple
101         * accesses to the same field returns the same data, even if the
102         * underlying object has been updated in other transactions. If
103         * an attempt is made to subsequently update the data after
104         * another transaction has made changes, a deadlock exception is
105         * thrown to retry the transaction.
106         */
107        READ_COMMITTED_SNAPSHOT,
108
109        /**
110         * All access is serialized using shared read and exclusive
111         * write locks.
112         */
113        SERIALIZABLE
114    }
115
116    /**
117     *  Invalid transaction state exception.
118     *  <p>
119     *  Exception thrown if execute() is called with a transaction
120     *  already active, or if the transaction services are not available.
121     */
122    public static class InvalidTransactionState extends java.lang.Error
123    {
124
125        /**
126         *  Serialization version.
127         */
128        public final static long serialVersionUID = 1L;
129
130        InvalidTransactionState(String message)
131        {
132            super(message);
133        }
134    }
135
136    /**
137     *  Not transactional exception.
138     *  <p>
139     *  Exception thrown if one of the lock methods is called
140     *  on a non-transactional object.
141     */
142    public static class NotTransactional extends java.lang.Error
143    {
144        /**
145         *  Serialization version.
146         */
147        public final static long serialVersionUID = 1L;
148
149        NotTransactional(String message)
150        {
151            super(message);
152        }
153    }
154
155    /**
156     *  Rollback exception.
157     *  <p>
158     *  Exception thrown in the run method to rollback the transaction.
159     *  <p>
160     *  If constructed with a cause, the cause is propagated through the
161     *  execute() call via the InvocationRunException exception. 
162     */
163    public static class Rollback extends java.lang.Exception
164    {
165        /**
166         *  Serialization version.
167         */
168        public final static long serialVersionUID = 1L;
169
170        /**
171         *  Creates a rollback exception.
172         */
173        public Rollback()
174        {
175            super();
176        }
177
178        /**
179         *  Creates a rollback exception that is propagated to the caller.
180         *  @param  cause   The Throwable causing the exception.
181         */
182        public Rollback(Throwable cause)
183        {
184            super(cause);
185        }
186
187        /**
188         *  Creates a rollback exception that is propagated to the caller.
189         *  @param  message Message to include in the exception.
190         *  @param  cause   The throwable causing the exception.
191         */
192        public Rollback(String message, Throwable cause)
193        {
194            super(message, cause);
195        }
196    }
197
198    /**
199     *  Invocation run exception.
200     *  <p>
201     *  Exception thrown by the execute method when the enclosed
202     *  <code>run</code> throws a Rollback exception with a cause.
203     */
204    public static class InvocationRunException
205        extends java.lang.RuntimeException
206    {
207        /**
208         *  Serialization version.
209         */
210        public final static long serialVersionUID = 1L;
211
212        /**
213         *  Creates an exception that is propagated to the caller.
214         *  @param  message Message to include in the exception.
215         *  @param  cause   The throwable causing the exception.
216         */
217        public InvocationRunException(String message, Throwable cause)
218        {
219            super(message, cause);
220        }
221    }
222
223    /**
224     *  Transaction identifier.
225     *  <p>
226     *  An identifier for the current transaction. Transaction
227     *  identifiers are unique per JVM instance.  They are not
228     *  unique across JVM restarts.
229     *  <p>
230     *  Multiple transaction identifiers created in the same
231     *  transaction will return the same hash code, and comparisons
232     *  using the equals() method will return true.
233     *  <p>
234     *  Transaction identifiers are provided for logging purposes
235     *  only.
236     */
237    public static class Identifier
238    {
239        private String  m_id;
240
241        private Identifier()
242        {
243            m_id = getId();
244        }
245
246        /**
247         *  Returns a string representation of a transaction identifier.
248         *  @return  The string representation of a transaction identifier.
249         */
250        @Override
251        public String toString()
252        {
253            return m_id;
254        }
255
256        /**
257         *  Determines if two transaction identifiers are the same.
258         *  @param id Transaction identifier to compare
259         * @return     True if both objects represent the
260         *          same transaction identifier.
261         */
262        @Override
263        public boolean equals(Object id)
264        {
265            if (!(id instanceof Identifier))
266            {
267                return false;
268            }
269            return m_id.equals(((Identifier)id).m_id);
270        }
271
272        /**
273         *  Returns a hash value for this transaction identifier.
274         *  @return     A hash code value for this transaction identifier.
275         */
276        @Override
277        public int hashCode()
278        {
279            return m_id.hashCode();
280        }
281    }
282
283    /**
284     * The Transaction properties used when executing a transaction
285     */
286    public final static class Properties
287    {
288        /**
289         * Default constructor, sets properties to default values:
290         * <p>
291            isolationLevel = {@link IsolationLevel#SERIALIZABLE},
292            transactionName is null.
293         */
294        public Properties()
295        {
296            //
297            // FIX THIS: We can't access the class name of the outer
298            // class, the getEnclosingClass() method will always return
299            // "Transaction".
300            //
301            m_isolationLevel = IsolationLevel.SERIALIZABLE;
302            m_transactionName = null;
303        }
304        /**
305         * Construct a properties instance with the given isolation level
306         * and a null transaction name.
307         *
308         * @param isolationLevel - Isolation level of transaction
309         *  when it executes.
310         */
311        public Properties(IsolationLevel isolationLevel)
312        {
313            m_isolationLevel = isolationLevel;
314            m_transactionName = null;
315        }
316
317        /**
318         * Construct a properties instance with the given name and a
319         * default isolationLevel of {@link IsolationLevel#SERIALIZABLE}
320         *
321         * @param transactionName - Name of transaction when it executes.
322         */
323        public Properties(String transactionName)
324        {
325            m_transactionName = transactionName;
326            m_isolationLevel = IsolationLevel.SERIALIZABLE;
327        }
328
329        /**
330         * Construct a properties instance with the given name and
331         * isolation level.
332         *
333         * @param transactionName - Name of transaction when it executes.
334         * @param isolationLevel - Isolation level of transaction
335         *  when it executes.
336         */
337        public Properties(String transactionName, IsolationLevel isolationLevel)
338        {
339            m_transactionName = transactionName;
340            m_isolationLevel = isolationLevel;
341        }
342        /**
343         * Set the transaction name.
344         *
345         * @param transactionName - Name of transaction when executed.
346         */
347        public void setTransactionName(String transactionName)
348        {
349            m_transactionName = transactionName;
350        }
351        /**
352         * Get the transaction name.
353         * <p>
354         * If never set, returns null. If passed into the {@link
355         * Transaction#execute(Properties)} method as null, the name
356         * passed into the {@link Transaction#Transaction(String)}
357         * constructor is used, or the calling class name is used if the 
358         * default {@link Transaction#Transaction()} constructor was used.
359         *
360         * @return String containing transaction name.
361         */
362        public String getTransactionName()
363        {
364            return m_transactionName;
365        }
366        /**
367         * Set the isolation level.
368         *
369         * @param isolationLevel - Isolation level of transaction
370         *  when it executes.
371         */
372        public void setIsolationLevel(IsolationLevel isolationLevel)
373        {
374            m_isolationLevel = isolationLevel;
375        }
376        /**
377         * Get the isolation level.
378         *
379         * @return Current isolation level.
380         */
381        public IsolationLevel getIsolationLevel()
382        {
383            return m_isolationLevel;
384        }
385
386        private String m_transactionName;
387        private IsolationLevel m_isolationLevel;
388    }
389
390    /** Locked object notifier
391      * <p>
392      * This notifier is used to access all locked objects in a
393      * transaction.  The abstract method is called once for
394      * each locked object in a transaction.
395      * <p>
396      * The ObjectNotifier is executed using the
397      * ObjectServices::runObjectNotifier static method.
398      * <p>
399      * Typically, the ObjectNotifier is used in conjunction with
400      * the TransactionNotifier to process objects at prepare or
401      * commit time.
402      * <p>
403      * Note: the notifier will not be invoked for objects created and
404      * deleted in the current transaction.
405      */
406    public static abstract class ObjectNotifier
407    {
408        /** Called for each object locked in a transaction.
409         * <p>
410         * Will not be invoked for objects created and
411         * deleted in the current transaction.
412         * @param obj Object locked in transaction.
413         */
414        protected abstract void inTransaction(final Object obj);
415    }
416
417    /**
418     * Creates a new transaction.
419     * @param transactionName - Name associated with the transaction.
420     */ 
421    public Transaction(final String transactionName)
422    {
423        m_defaultProperties = new Properties(transactionName);
424    }
425
426    /**
427     *  Creates a new transaction.
428     */ 
429    public Transaction()
430    {
431        m_defaultProperties = new Properties(getClass().getName());
432    }
433
434    /**
435     *  User defined method that is run in the context of a transaction.
436     *  
437     *  @exception com.kabira.platform.Transaction.Rollback
438     *      Thrown if the transaction should be rolled back
439     *      and all changes discarded.
440     */
441    protected abstract void run() throws Transaction.Rollback;
442
443    /**
444     * Executes the user defined method within a transaction. Any
445     * deadlocks are transparently retried.
446     *
447     * @param properties - Properties used when executing the transaction.
448     *
449     * @return Result of transaction
450     *
451     * @throws InvalidTransactionState
452     *  If a transaction is already active, or the transaction services
453     *  are not available.
454     * @throws InvocationRunException
455     *  The execution of the run method resulted in an exception
456     *  that was propagated.
457     */
458    public final Result execute(Properties properties)
459        throws InvalidTransactionState, InvocationRunException
460    {
461        String transactionName = properties.getTransactionName();
462        if (transactionName == null)
463        {
464            transactionName = getClass().getName();
465        }
466        assert( properties.getIsolationLevel() ==
467                    IsolationLevel.READ_COMMITTED_SNAPSHOT ||
468                properties.getIsolationLevel() == 
469                    IsolationLevel.SERIALIZABLE );
470        int isolationLevel = (properties.getIsolationLevel() ==
471                IsolationLevel.READ_COMMITTED_SNAPSHOT)
472                    ? ReadCommittedSnapshot : Serializable;
473
474        boolean done = false;
475        Result  result = Result.ROLLBACK;
476
477        m_numDeadlocks = 0;
478
479        while (!done)
480        {
481            begin(transactionName, isolationLevel);
482            try
483            {
484                run();
485                prepare();
486                commit();
487                result = Result.COMMIT;
488                done = true;
489            }
490            catch (Rollback ex)
491            {
492                abort(ROLLBACK_ABORT);
493                if (ex.getCause() != null)
494                {
495                    throw new InvocationRunException(
496                        ex.getMessage(), ex.getCause());
497                }
498                done = true;
499            }
500            catch (com.kabira.ktvm.transaction.DeadlockError ex)
501            {
502                abort(DEADLOCK_ABORT);
503
504                StringWriter stringWriter = new StringWriter();
505                PrintWriter printWriter = new PrintWriter(stringWriter);
506                ex.printStackTrace(printWriter);
507
508                deadlockBackoff(ex.getDeadlockType(),
509                        stringWriter.toString(), ++m_numDeadlocks);
510            }
511            catch (java.lang.Error ex)
512            {
513                abort(EXCEPTION_ABORT);
514                throw ex;
515            }
516            catch (java.lang.RuntimeException ex)
517            {
518                abort(EXCEPTION_ABORT);
519                throw ex;
520            }
521        }
522        return result;
523    }
524
525    /**
526     *  Executes the user defined method within a transaction. Any
527     *  deadlocks are transparently retried. 
528     * <p>
529     * The default isolationLevel of SERIALIZABLE is used for the
530     * transaction.
531     *
532     * @return Result of transaction
533     * @throws InvalidTransactionState
534     *  If a transaction is already active, or the transaction services
535     *  are not available.
536     * @throws InvocationRunException
537     *  The execution of the run method resulted in an exception
538     *  that was propagated.
539     */
540    public final Result execute()
541        throws InvalidTransactionState, InvocationRunException
542    {
543        return execute(m_defaultProperties);
544    }
545
546    /**
547     *  Returns the transaction identifier for the current transaction.
548     *
549     *  This only returns a valid transaction identifier when there
550     *  is an active transaction.
551     *
552     *  @throws java.lang.IllegalAccessError
553     *  No active transaction
554     *
555     *  @return The current transaction identifier.
556     */
557    public static final Identifier getIdentifier()
558        throws IllegalAccessError
559    {
560        return new Identifier();
561    }
562
563    /**
564     * Returns the transaction properties for the current transaction.
565     *
566     * This only returns the properties when there is an active transaction.
567     *
568     * @throws java.lang.IllegalAccessError
569     *  No active transaction
570     *
571     * @return The properties for the given active transaction.
572     */
573    public static final Properties getActiveTransactionProperties()
574        throws IllegalAccessError
575    {
576        String transactionName = getTransactionName();
577
578        int ilevel = getIsolationLevel();
579        assert( ilevel == ReadCommittedSnapshot || ilevel == Serializable );
580
581        IsolationLevel isolationLevel = (ilevel == ReadCommittedSnapshot)
582            ? IsolationLevel.READ_COMMITTED_SNAPSHOT
583            : IsolationLevel.SERIALIZABLE;
584
585        return new Properties(transactionName, isolationLevel);
586    }
587
588    /**
589     * Determine if object was created in the current transaction.
590     * <p>
591     * This operation can be used to determine if the specified object
592     * was created in the current transaction.
593     *
594     * @param   obj Object to check
595     * @throws  java.lang.IllegalAccessError     No active transaction
596     * @throws  ManagedClassError       If obj is not Managed.
597     * @return  true if the object was created.
598     */
599    public static boolean createdInTransaction(Object obj)
600        throws IllegalAccessError 
601    {
602        return _createdInTransaction(
603            ManagedObject.getObjectReference(obj, "createdInTransaction"));
604    }
605
606    /**
607     * Determine if object was deleted in the current transaction.
608     * <p>
609     * This operation can be used to determine if the specified object
610     * was deleted in the current transaction.
611     * <p>
612     * <b>Warning</b>:
613     * If an instance has been deleted in a transaction, any attempt
614     * to cast the instance to a subtype will return an empty handle. 
615     *
616     * @param   obj Object to check
617     * @throws  java.lang.IllegalAccessError     No active transaction
618     * @throws  ManagedClassError       If obj is not Managed.
619     * @return  true if the object was deleted.
620     */
621    public static boolean deletedInTransaction(Object obj)
622    {
623        return _deletedInTransaction(
624            ManagedObject.getObjectReference(obj, "deletedInTransaction"));
625    }
626
627    /**
628     * Determine if object was modified in the current transaction.
629     * <p>
630     * This method can be used to determine if an object
631     * was modified in the current transaction.  An object is
632     * considered to have been modified if it was created or updated
633     * in the current transaction.
634     *
635     * @param   obj Object to check
636     * @throws  java.lang.IllegalAccessError     No active transaction
637     * @throws  ManagedClassError       If obj is not Managed.
638     * @return  true if the object was modified.
639     */
640    public static boolean modifiedInTransaction(Object obj)
641    {
642        return _modifiedInTransaction(
643            ManagedObject.getObjectReference(obj, "modifiedInTransaction"));
644    }
645
646    /** Run an object notifier
647      * <p>
648      * When called, the inTransaction method in the ObjectNotifier is
649      * called for each locked object in the current transaction.
650      * @param objectNotifier Application notifier.
651      * @throws  java.lang.IllegalAccessError     No active transaction
652      */
653    public static void runObjectNotifier(ObjectNotifier objectNotifier)
654    {
655        _runObjectNotifier(objectNotifier);
656    }
657
658    /**
659     *  Determines if a transaction is active.
660     *
661     *  @return true if a transaction is active, false otherwise.
662     */
663    public native static boolean isActive();
664
665    /**
666     * Returns the current setting of the transaction description.
667     *
668     * @return Description, as set via setTransactionDescription().
669     */
670    public native static final String getTransactionDescription();
671
672    /**
673     * Sets the current transaction description.
674     *
675     * The description is cleared when the transaction begins.
676     *
677     * @param description The value to set.
678     */
679    public native static final void setTransactionDescription(
680            String description);
681
682    /**
683     *  Returns the number of deadlocks detected and replayed for the last
684     *  call to execute().
685     *
686     *  @return     The number of detected deadlocks.
687     */
688    public final int getNumberDeadlocks()
689    {
690        return m_numDeadlocks;
691    }
692
693    /**
694     *  Establishes a write lock for the given object.
695     *
696     *  @param  obj Object to lock.
697     *
698     *
699     *  @throws java.lang.IllegalAccessError
700     *  No active transaction
701     *
702     *  @throws NotTransactional
703     *  If the object passed in is not Managed.
704     */
705    public static final void writeLockObject(Object obj)
706        throws IllegalAccessError, NotTransactional
707    {
708        validateObject(obj, "writeLockObject");
709        lockObject(WRITE_LOCK, ManagedObject.getObjectReference(obj));
710    }
711
712    /**
713     * Establishes a read lock for the given object.
714     * <p>
715     * If the current transaction isolation level is {@link
716     * IsolationLevel#READ_COMMITTED_SNAPSHOT}, a snapshot of the data is
717     * taken and the read lock immediately released.
718     *
719     * @param  obj Object to lock.
720     *
721     * @throws java.lang.IllegalAccessError
722     *  No active transaction
723     *
724     * @throws NotTransactional
725     *  If the object passed in is not Managed.
726     */
727    public static final void readLockObject(Object obj)
728        throws IllegalAccessError, NotTransactional
729    {
730        validateObject(obj, "readLockObject");
731        lockObject(READ_LOCK, ManagedObject.getObjectReference(obj));
732    }
733
734    /**
735     * Determines if a write lock exists for an object in the current transaction.
736     *
737     *  @param  obj Object to examine.
738     *
739     *  @throws java.lang.IllegalAccessError
740     *  No active transaction
741     *
742     *  @throws NotTransactional
743     *  If the object passed in is not Managed.
744     *
745     *  @return true if a write lock exists on
746     *          object in current transaction, false otherwise.
747     */
748    public static final boolean hasWriteLock(Object obj)
749        throws IllegalAccessError, NotTransactional
750    {
751        validateObject(obj, "hasWriteLock");
752        return hasLock(WRITE_LOCK, ManagedObject.getObjectReference(obj));
753    }
754
755    /**
756     * Determines if a read lock exists for an object in the current transaction.
757     * <p>
758     * If the current transaction isolation level is {@link
759     * IsolationLevel#READ_COMMITTED_SNAPSHOT}, readlocks are immediately
760     * released after a snapshot is taken. In this case hasReadLock()
761     * will always return false.
762     *
763     * @param  obj Object to examine
764     *
765     * @throws java.lang.IllegalAccessError
766     *  No active transaction
767     *
768     * @throws NotTransactional
769     *  If the object passed in is not Managed.
770     *
771     * @return  true if a read lock exists on
772     *          object in current transaction, false otherwise.
773     */
774    public static final boolean hasReadLock(Object obj)
775        throws IllegalAccessError, NotTransactional
776    {
777        validateObject(obj, "hasReadLock");
778        return hasLock(READ_LOCK, ManagedObject.getObjectReference(obj));
779    }
780
781    //
782    // Validate the object can be used in a transaction.
783    //
784    private static void validateObject(Object obj, String op)
785    {
786        if (! ManagedObject.isManaged(obj))
787        {
788            throw new NotTransactional(op + " cannot be used " +
789                "on a non-Managed object");
790        }
791    }
792
793    //
794    // These must match the LockType enum used in the C++ native methods
795    //
796    private static final int READ_LOCK = 1;
797    private static final int WRITE_LOCK = 2;
798    //
799    // These must match the ObjSrv::TxnIsolationLevel enum used in the
800    // C++ native methods.
801    //
802    private static final int Serializable = 1;
803    private static final int ReadCommittedSnapshot = 2;
804    //
805    // These must match the ObjectType enum used in the C++ native methods
806    //
807    private static final int JAVA_OBJECT = 1;
808    private static final int MANAGED_OBJECT = 2;
809    //
810    // These must match the ObjSrv::AbortReason enum used in the
811    // C++ native methods
812    //
813    private static final int DEADLOCK_ABORT = 1;
814    private static final int ROLLBACK_ABORT = 2;
815    private static final int EXCEPTION_ABORT = 3;
816
817    //
818    // Used to manage getNumDeadlocks()
819    //
820    private int m_numDeadlocks;
821
822    //
823    // Properties for transaction
824    //
825    private Properties m_defaultProperties;
826
827    //
828    // native methods
829    //
830    private native static void begin(
831        final String transactionName, int isolationLevel)
832            throws InvalidTransactionState;
833    private native void prepare();
834    private native void commit();
835    private native static void abort(int abortReason);
836    private native static void deadlockBackoff(
837        int deadlockType, String tranName, int numDeadlocks);
838    private native static void lockObject(
839        int lockType, final byte[] objRef);
840    private native static boolean hasLock(
841        int lockType, final byte[] objRef);
842    private native static final String getId();
843    private native static final String getTransactionName();
844    private native static final int getIsolationLevel();
845    private native static final boolean _createdInTransaction(
846        final byte[] objectReference);
847    private native static final boolean _deletedInTransaction(
848        final byte[] objectReference);
849    private native static final boolean _modifiedInTransaction(
850        final byte[] objectReference);
851    private native static void _runObjectNotifier(
852            ObjectNotifier objectNotifier);
853}