Class Tracker


  • public class Tracker
    extends java.lang.Object
    Provides an interface to a database location tracking set. Refer to Oracle's documentation for details of how a tracking set works.

    Typical usage:

    Set optional parameters using the available methods, and optionally create and start the tracking set on the server (if not already done). Once setup is finished, call the begin() method. After begin() is called, you can set or delete tracking regions on the server, subscribe to notifications using setNotification(long, long, NotificationType), and start sending locations and receiving and processing notifications.

    To connect to an existing, running tracking set named "fleet" owned by user "john", and send a location update that objectId 21 is at (x1, y1) and object 27 is at (x2, y2):

     
     Tracker tracker = new Tracker("john", "fleet", oracleDataSource).begin();
     tracker.location(21, x1, y1);
     tracker.location(27, x1, y1);
     tracker.sendLocations();
     
     

    To use "fleet" if it exists, or create and/or start it if necessary (with 3 tracking queues and 2 location queues), with the capability of setting callbacks on regions:

     
     Tracker tracker = new Tracker("john", "fleet", oracleDataSource);
     tracker.setNumTrackingQueues(3);         // Value used only if fleet needs to be created
     tracker.setNumLocationQueues(2);         // Value used only if fleet needs to be created
     tracker.allowRegionCallbacks(true);
     tracker.startTrackingSet(true);          // Start if not already started; create if it doesn't already exist
     tracker.begin();
     
     

    After begin() is called, location messages can be sent to the server and notifications can be received and processed.

    List of permissions that may be needed by the database user of the tracking set: connect, resource, aq_administrator_role, create job, manage scheduler, dbms_aq, dbms_aqadm, dbms_lock, dbms_aqin, dbms_aqjms

    You can use the following SQL command to check the number of messages in the queues on the server:
    Select a.queue_table, b.* from dba_queues a, v$aq b where b.ready > 0 and a.qid = b.qid;

    Concurrency:

    Each Tracker object has a single session and must be single-threaded. Use the newSession() method to create duplicate Tracker objects with separate sessions; each such duplicate can be used by a different thread. You can create dedicated workers using the createWorkerThread call, which returns a new Thread with its own session.

    The group of Trackers and workers created via newSession() and createWorker() share common location and notification queues with the original Tracker, and a common set of callbacks. Efficiency is generally improved by the ability to batch multiple messages to and from the database, and the ability of multiple threads to process incoming notifications.

    If you instead use new to create multiple Trackers for the same database tracking set, then location messages from separate Trackers can not be batched together. Notifications fetched from the database by one Tracker will not be available to other Trackers. Callbacks, if used, are set separately for each of the new'd Trackers, and so a notification will be processed according to whichever Tracker receives it. Subject to these limitations, multiple Tracker objects are supported, and are useful for applications running on multiple computers creating location messages.

    Database processing is concurrent, and it is possible for location messages to be processed in a different order than what they were sent in. For similar reasons, location messages sent immediately after a setNotification() subscription may be processed before the setNotification() is completed on the server. While these variations occur for all notification types, the effects are most dramatic for TRANSITION notifications. A sequence of locations like "out1 out2 out3 in4 in5 in6" you would expect to generate two TRANSITION notifications "out1 in4", can instead (if the locations are sent very close together) generate potentially up to six notifications, "out1 in4 out2 in5 out3 in6". If order is important, you must resolve these using the timestamp field of the messages.

    Example code

     
       static volatile boolean stopWork = false;
       static void example(OracleDataSource oracleDataSource) throws SQLException, JMSException, InterruptedException {
         
         // Connect to database and start a tracking set named "sample", creating it if necessary
         Tracker sample = new Tracker("sdobug", "sample", oracleDataSource)
               .stopDrop()                // For this example, delete & re-create queues to make sure no old data from previous run
               .startTrackingSet(true)       // Create and start the tracking set
               .begin();                     // Must call begin before communicating with database
     
         Tracker.debugLevel(3);              // Request debugging information
         
         // Set up a tracking region with id 77
         JGeometry geom = new JGeometry(2003, SRID, new int[] {1,1003,1}, new double[] {11,2, 20,2, 20,14, 11,14, 11,2});
         sample.setTrackingRegion(77, geom);
         // Track whenever object 1 enters or leaves region 77
         sample.setNotification(1, 77, Tracker.NotificationType.TRANSITION);
         
         // Start a location worker to send locations and a notification worker to fetch notifications from server
         Thread workerL = sample.createWorkerThread(WorkerType.LOCATION_WORKER, () -> stopWork);
         workerL.start();
         Thread workerN = sample.createWorkerThread(WorkerType.NOTIFICATION_WORKER, () -> stopWork);
         workerN.start();
         
         // Setup processing for incoming notification
         sample.setCallback(msg -> System.out.println("Object "+msg.getObjectId()
                   +" ("+msg.getX()+", "+msg.getY()
                   +") is "+msg.getState()+" region "+msg.getRegionId()));
         
         // Send a location
         sample.location(1,   1.10,  1.20, new Timestamp(69,  7, 20, 20, 39, 33, 0)); 
         sample.location(1,   2,     3,    new Timestamp(69,  7, 20, 59, 18,  0, 0));
         sample.location(1,  15,    10,    new Timestamp(69, 11, 19,  5, 32,  0, 0));
         // (sendLocations() call not needed because we have a LOCATION_WORKER running)
         
         // Expect to see two notifications, (1.10, 1.20) OUTSIDE and (15, 10) INSIDE
         
         // Wait 20 seconds, and then tell the workers to quit
         Thread.sleep(20000);
         
         stopWork = true;
         workerL.interrupt();
         workerL.join();
         workerN.interrupt();
         workerN.join();
     
         // Done, close the java object (but leave the database tracking set intact)
         sample.close();
     
       }
     
     

    Since:
    18.1
    • Nested Class Summary

      Nested Classes 
      Modifier and Type Class Description
      static interface  Tracker.Callback<T>
      Equivalent to consumer lambda, but allows SQLException and JMSException.
      static class  Tracker.NotificationType
      The type of notifications that can be subscribed to
      static class  Tracker.WorkerType
      Types of worker threads that can be created
    • Constructor Summary

      Constructors 
      Constructor Description
      Tracker​(java.lang.String owner, java.lang.String name, oracle.jdbc.pool.OracleDataSource ods)
      Create a Tracker for accessing the tracking set owner.name on the database.
    • Constructor Detail

      • Tracker

        public Tracker​(java.lang.String owner,
                       java.lang.String name,
                       oracle.jdbc.pool.OracleDataSource ods)
                throws java.sql.SQLException,
                       javax.jms.JMSException
        Create a Tracker for accessing the tracking set owner.name on the database. After setting up options, call begin() to activate.

        This object must only be used by a single thread. use Tracker(Tracker) to duplicate this for use in a separate thread.

        Parameters:
        owner - the owner (schema) of the queue
        name - the name of this tracker
        ods - Oracle data source
        Throws:
        java.sql.SQLException - for problems communicating with the database
        javax.jms.JMSException - from database
    • Method Detail

      • newSession

        public Tracker newSession()
                           throws javax.jms.JMSException,
                                  java.sql.SQLException
        Create a duplicate of this tracker with its own session (each thread needs its own session). All other data (including callback settings, location and notification queues) is shared. Note that callbacks may be processed by any thread or worker requesting notifications.

        The alternative of creating a separate tracker with new Tracker(...) will result in separate queues and callbacks on the client side and may reduce efficiency.

        Returns:
        new Duplicate tracker suitable for use in a separate thread
        Throws:
        javax.jms.JMSException - on database error
        java.sql.SQLException - on database error
      • createWorkerThread

        public java.lang.Thread createWorkerThread​(Tracker.WorkerType workerType,
                                                   java.util.function.BooleanSupplier exitFlag)
                                            throws javax.jms.JMSException,
                                                   java.sql.SQLException
        Create a new session of this tracker usable as a worker thread. All other data (including callback settings, location and notification queues) is shared. Note that callbacks may be processed by any thread requesting notifications.

        Caller is responsible for calling Thread.start() on the returned object.

        To terminate thread, arrange for exitFlag to return true and interrupt the thread.

        Parameters:
        workerType - the type of worker to create
        exitFlag - worker will run until this lambda returns true (typically refers to a volatile boolean)
        Returns:
        new Duplicate tracker suitable for use in a separate thread
        Throws:
        javax.jms.JMSException - on database error
        java.sql.SQLException - on database error
      • setNumLocationQueues

        public void setNumLocationQueues​(int numLocationQueues)
        Set the number of location queues we should use when creating a new tracking set on the database. Does not affect an existing tracking set.
        Parameters:
        numLocationQueues - the number of queues to create
      • setNumTrackingQueues

        public void setNumTrackingQueues​(int numTrackingQueues)
        Set the number of tracking queues we should use when creating a new tracking set on the database. Does not affect an existing tracking set.
        Parameters:
        numTrackingQueues - the number of queues to create
      • allowRegionCallbacks

        public Tracker allowRegionCallbacks​(boolean allowed)
        Whether or not per-region callbacks will be used in this Tracker. There is some minimal overhead
        Parameters:
        allowed - set true to allow per-region callbacks to be set
        Returns:
        this Tracker
      • setNotificationQueueSize

        public void setNotificationQueueSize​(int maxSize)
        The number of notifications without callbacks to store before discarding the oldest
        Parameters:
        maxSize - maximum number of unprocessed notifications to store; older ones will be dropped to make room for new.
      • allowObjectCallbacks

        public Tracker allowObjectCallbacks​(boolean allowed)
        Whether or not per-object callbacks will be used in this Tracker. There is some minimal overhead.
        Parameters:
        allowed - true to allow per-object callbacks to be set
        Returns:
        this Tracker
      • debugOutput

        public static void debugOutput​(java.io.PrintStream ps)
        Copy debugging output to the given print stream, in addition to its normal output location. Logging level must also be set. This setting is global for all Tracker objects.

        Example usage

         
         Tracker.debugOutput(Sysetm.err);
         Tracker.debugLevel(3);
         
         

        Parameters:
        ps - the print stream to log to
      • debugLevel

        public static void debugLevel​(int level)
        Set level of debug output (overriding any system value). Recommend setting this before creating Tracker objects. This setting is global for all Tracker objects.
        Parameters:
        level - Zero (most detailed) through 9 (fatal errors only). Three is a good starting point.
      • stopTrackingSet

        public Tracker stopTrackingSet()
                                throws java.sql.SQLException
        Stop the tracker on the database.
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - from database operation
      • startTrackingSet

        public Tracker startTrackingSet​(boolean createIfNecessary)
                                 throws java.sql.SQLException,
                                        javax.jms.JMSException
        Connect to an existing tracking set on the database; optionally create if it doesn't already exist. Then start it on the database if not already started.
        Parameters:
        createIfNecessary - if true and tracking set doesn't exist on database, create one
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - from database operation
        javax.jms.JMSException - from database operation
      • begin

        public Tracker begin()
                      throws java.sql.SQLException,
                             javax.jms.JMSException
        Marks the end of setup. After this call, can send locations, receive notifications, set tracking regions and subscribe to notifications, and create new sessions and workers.
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - from database operation
        javax.jms.JMSException - from database operation
      • stopDrop

        public Tracker stopDrop()
                         throws java.sql.SQLException
        Stop (if running) and drop the tracker on the database, cleaning up all database resources. Should not be followed by any operation other than close().
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - from database operation
      • createTrackingSet

        public Tracker createTrackingSet()
                                  throws java.sql.SQLException
        Create the tracker on the database. Does not issue a startTrackingSet(boolean). It is an error if the tracker already exists.
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - from database operation
      • setTrackingRegion

        public Tracker setTrackingRegion​(long regionId,
                                         JGeometry geom)
                                  throws java.sql.SQLException,
                                         javax.jms.JMSException
        Set a tracking region on the server.
        Parameters:
        regionId - an integer identifying the region
        geom - the corresponding geometry (polygon or multipolygon)
        Returns:
        this tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • deleteTrackingRegion

        public Tracker deleteTrackingRegion​(long regionId,
                                            boolean deleteNotifications)
                                     throws java.sql.SQLException,
                                            javax.jms.JMSException
        Delete a tracking region. Tracker must be started before any region or location operations.
        Parameters:
        regionId - the ID of the region to remove
        deleteNotifications - if true, subscriptions matching the regionId are also deleted
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • deleteObjectSubscription

        public Tracker deleteObjectSubscription​(long objectId,
                                                java.lang.Long regionId)
                                         throws java.sql.SQLException,
                                                javax.jms.JMSException
        Delete notifications for a tracked object. Tracker must be started before any region or location operations.
        Parameters:
        objectId - the ID of the object to no longer track
        regionId - the ID of a particular region, or null for all regions
        Returns:
        this Tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • location

        public void location​(long objId,
                             double x,
                             double y,
                             java.sql.Timestamp timestamp)
                      throws java.sql.SQLException,
                             javax.jms.JMSException
        Queues a location message to be sent to the server. You must separately send the queued messages, either by calling sendLocations() from this Tracker or any of its duplicate sessions, or by creating a worker thread, createWorkerThread(WorkerType.LOCATION_WORKER, flag).start().
        Parameters:
        objId - the object's ID
        x - the x-value of the location
        y - the y-value of the location
        timestamp - the timestamp of this location message
        Throws:
        java.sql.SQLException - from database
        javax.jms.JMSException - from database
      • location

        public void location​(long objId,
                             double x,
                             double y)
                      throws java.sql.SQLException,
                             javax.jms.JMSException
        Parameters:
        objId - the object's ID
        x - the x-value of the location
        y - the y-value of the location
        Throws:
        java.sql.SQLException - from database
        javax.jms.JMSException - from database
      • sendLocations

        public void sendLocations()
                           throws javax.jms.JMSException,
                                  java.sql.SQLException
        Send any queued locations to the server. Necessary if there is no LOCATION worker thread processing the queue.
        Throws:
        javax.jms.JMSException - from database operation
        java.sql.SQLException - from database operation
      • setNotification

        public Tracker setNotification​(long objId,
                                       long regionId,
                                       Tracker.NotificationType type)
                                throws java.sql.SQLException,
                                       javax.jms.JMSException
        Subscribe to notifications about a particular object/region pair
        Parameters:
        objId - an integer identifying the object to be notified about
        regionId - an integer identifying the region
        type - INSIDE/OUTSIDE/TRANSITION
        Returns:
        this tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • setNotification

        public Tracker setNotification​(TrackerMsg msg)
                                throws java.sql.SQLException,
                                       javax.jms.JMSException
        Add a single subscription to the database
        Parameters:
        msg - the subscription
        Returns:
        this tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • setNotifications

        public Tracker setNotifications​(java.util.List<TrackerMsg> subscriptionList)
                                 throws java.sql.SQLException,
                                        javax.jms.JMSException
        Set multiple subscriptions on the server.
        Parameters:
        subscriptionList - the list of subscriptions
        Returns:
        this tracker
        Throws:
        java.sql.SQLException - on database error
        javax.jms.JMSException - on database error
      • setRegionCallback

        public void setRegionCallback​(long regionId,
                                      Tracker.Callback<NotificationMsg> callback)
        When a notification occurs for the given regionId, forward the notification to the specified callback method.

        You must also do setNotification(objectId, regionId, NotificationType) for every (object, region) combination you want the server to notify you of.

        This method is thread-safe and may be called before or after begin (but after allowRegionCallbacks)

        Parameters:
        regionId - the region we are interested in
        callback - the method to call when we have a notification for regionId, or null to delete any existing callback for regionId
      • setObjectCallback

        public void setObjectCallback​(long objectId,
                                      Tracker.Callback<NotificationMsg> callback)
        When a notification occurs for the given objectId, forward the notification to the specified callback method.

        You must also do setNotification(objectId, regionId, NotificationType) for every (region, object) combination you want the server to notify you of.

        This method is thread-safe and may be called before or after begin (but after allowObjectCallbacks)

        Parameters:
        objectId - the object we are interested in
        callback - the method to call when we have a notification for objectId, or null to remove any existing callback for objectId
      • setCallback

        public void setCallback​(Tracker.Callback<NotificationMsg> callback)
        Set a callback for all notifications that are not otherwise covered by a region or object callback.

        If this is set, no notifications will be returned by getNotification() or getNotifications(long). Those calls are still useful for pulling notifications from the server and processing them if you are not using a NOTIFICATION worker.

        This method is thread-safe and may be called at any time.

        Parameters:
        callback - the callback to execute, or null to remove an existing callback
      • getNotifications

        public java.util.List<NotificationMsg> getNotifications​(long waitMs)
                                                         throws javax.jms.JMSException,
                                                                java.sql.SQLException
        Return all available notifications. If none are available, waits up to msWait for one to arrive. Arriving notifications are shared between duplicate sessions of this Tracker.

        Arriving notifications that match callbacks will be processed via those callbacks during this call and not returned.

        Parameters:
        waitMs - number of milliseconds to wait if none currently available: -1 means no wait, zero means infinite wait
        Returns:
        List of the received notifications (empty list if timed out waiting)
        Throws:
        java.sql.SQLException - propagated from database
        javax.jms.JMSException - propagated from database
      • getNotification

        public NotificationMsg getNotification​(long waitMs)
                                        throws javax.jms.JMSException,
                                               java.sql.SQLException
        Return one notification, waiting up to the specified time if there are no outstanding.

        Arriving notifications that match callbacks will be processed via those callbacks during this call and not returned. If all notifications match a callback, then this method will process notifications until the specified timeout. If you are using callbacks, consider using fetchNotifications() instead.

        Parameters:
        waitMs - length of time to wait (-1 = don't wait, 0 = wait forever)
        Returns:
        the notification, or null if none is available before the specified wait time
        Throws:
        javax.jms.JMSException - from database operation
        java.sql.SQLException - from database operation
      • getNotification

        public NotificationMsg getNotification()
                                        throws javax.jms.JMSException,
                                               java.sql.SQLException
        Return one notification, blocking if there are no outstanding

        Arriving notifications that match callbacks will be processed via those callbacks during this call and not returned. If all notifications match a callback, then this method will process notifications forever. If you are using callbacks, consider using fetchNotifications() instead.

        Returns:
        the notification
        Throws:
        javax.jms.JMSException - from database operation
        java.sql.SQLException - from database operation
      • fetchNotifications

        public int fetchNotifications​(long waitMs)
                               throws javax.jms.JMSException,
                                      java.sql.SQLException
        Fetch notification(s) from the database, waiting at most the specified time for the first one to arrive. Callback processing is done; notifications without callbacks are queued for calls to getNotification() to return (either in this session or a different session).

        fetchNotificaitons processes all notifications that have arrived (or arrive while it is processing) regardless of how long that takes, as long as the additional notifications are available without waiting.

        Parameters:
        waitMs - length of time to wait (-1 = don't wait, 0 = wait forever)
        Returns:
        the number of messages fetched
        Throws:
        javax.jms.JMSException - from database operation
        java.sql.SQLException - from database operation
      • close

        public void close()
                   throws javax.jms.JMSException,
                          java.sql.SQLException
        Close the connection to the database and release the client resources and database connections. The tracking set on the server is not stopped nor dropped.

        No further method calls can be made to this object after close(). Close() must be called on each Tracker object, since each has its own connection to the database.

        Throws:
        javax.jms.JMSException - on database error
        java.sql.SQLException - on database error
      • finalize

        public void finalize()
                      throws java.lang.Throwable
        Overrides:
        finalize in class java.lang.Object
        Throws:
        java.lang.Throwable
      • getStatistics

        public java.util.Map<java.lang.String,​java.lang.Long> getStatistics()
        Return statistics. Note that some statistics are not synchronized and may be inaccurate in a multi-threaded environment.

        The values reported and the labels given may change without notice. These are intended as a human-readable debugging aid.

        List of statistics collected
        maxQueuedLocationsThe high-water mark of locations waiting transmission to server
        notificationQueue current sizeCurrent number of notifications waiting for the user to read (excludes anything that matches a callback)
        notificationQueue maxQueueSizeSeenHigh-water mark of the notification queue
        notificationQueue numBatchLargerThanSizeLimitNumber of times a single insertion with more than sizeLimit notifications was attempted
        notificationQueue numQueuedNumber of notifications inserted into the queue
        notificationQueue numRemovedNumber of notifications retrieved by the user
        notificationQueue numSurplusNumber of notifications discarded due to reaching sizeLimit
        notificationQueue sizeLimitMaximum number of notifications stored before discarding oldest
        numGeneralCallbacks (approx)Number of notifications that went through the general callback instead of being queued
        numLocationsNumber of location calls made
        numMaxBatchesLocations are batched to server, up to a maximum size; this is the number of maximum-sized batches sent
        numObjectCallbacks (approx)Number of notifications that matched an objectId callback
        numObjectRegionCallbacks (approx)Number of notifications that matched both an objectId and regionId callback
        numRegionCallbacks (approx)Number of notifications that matched a regionId callback
        unsentLocationsLocations not yet sent to the server

        Returns:
        a map of statistics.
      • getOwner

        public java.lang.String getOwner()
        Return the database schema of this tracker.
        Returns:
        the schema
      • getName

        public java.lang.String getName()
        Returns:
        the name of the tracking set
      • trackingsetExists

        public boolean trackingsetExists()
        Indicates whether a tracking set of the given name has been created on the server, whether by us or from a previously existing tracker.
        Returns:
        cached value indicating whether tracking set of this name existed on the server at last check
      • getNotificationQueueSize

        public int getNotificationQueueSize()
        The number of notifications without callbacks to store before discarding the oldest
        Returns:
        max size of the incoming notification queue.
      • getNumLocationQueues

        public int getNumLocationQueues()
        If a tracking set of this name appears on the server, then this will return the number of location queues it has. Otherwise, returns the number of location queues we would create.
        Returns:
        num of location queues, at least one.
      • getNumTrackingQueues

        public int getNumTrackingQueues()
        If a tracking set of this name appears on the server, then this will return the number of tracking queues it has. Otherwise, returns the number of tracking queues we would create.
        Returns:
        num of tracking queues, at least one.