public class Tracker
extends java.lang.Object
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;
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.
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();
}
Modifier and Type | Class and 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 and Description |
---|
Tracker(java.lang.String owner,
java.lang.String name,
OracleDataSource ods)
Create a Tracker for accessing the tracking set owner.name on the database.
|
Modifier and Type | Method and Description |
---|---|
Tracker |
allowObjectCallbacks(boolean allowed)
Whether or not per-object callbacks will be used in this Tracker.
|
Tracker |
allowRegionCallbacks(boolean allowed)
Whether or not per-region callbacks will be used in this Tracker.
|
Tracker |
begin()
Marks the end of setup.
|
void |
close()
Close the connection to the database and release the client resources and database connections.
|
Tracker |
createTrackingSet()
Create the tracker on the database.
|
java.lang.Thread |
createWorkerThread(Tracker.WorkerType workerType,
java.util.function.BooleanSupplier exitFlag)
Create a new session of this tracker usable as a worker thread.
|
static void |
debugLevel(int level)
Set level of debug output (overriding any system value).
|
static void |
debugOutput(java.io.PrintStream ps)
Copy debugging output to the given print stream, in addition to its normal output location.
|
Tracker |
deleteObjectSubscription(long objectId,
java.lang.Long regionId)
Delete notifications for a tracked object.
|
Tracker |
deleteTrackingRegion(long regionId,
boolean deleteNotifications)
Delete a tracking region.
|
int |
fetchNotifications(long waitMs)
Fetch notification(s) from the database, waiting at most the specified time for the first one to arrive.
|
void |
finalize() |
java.lang.String |
getName() |
NotificationMsg |
getNotification()
Return one notification, blocking if there are no outstanding
|
NotificationMsg |
getNotification(long waitMs)
Return one notification, waiting up to the specified time if there are no outstanding.
|
int |
getNotificationQueueSize()
The number of notifications without callbacks to store before discarding the oldest
|
java.util.List<NotificationMsg> |
getNotifications(long waitMs)
Return all available notifications.
|
int |
getNumLocationQueues()
If a tracking set of this name appears on the server, then this will return the number of location queues it
has.
|
int |
getNumTrackingQueues()
If a tracking set of this name appears on the server, then this will return the number of tracking queues it
has.
|
java.lang.String |
getOwner()
Return the database schema of this tracker.
|
java.util.Map<java.lang.String,java.lang.Long> |
getStatistics()
Return statistics.
|
void |
location(long objId,
double x,
double y)
Same as calling
location(objId, x, y, new Timestamp(System.currentTimeMillis()) . |
void |
location(long objId,
double x,
double y,
java.sql.Timestamp timestamp)
Queues a location message to be sent to the server.
|
Tracker |
newSession()
Create a duplicate of this tracker with its own session (each thread needs its own session).
|
void |
sendLocations()
Send any queued locations to the server.
|
void |
setCallback(Tracker.Callback<NotificationMsg> callback)
Set a callback for all notifications that are not otherwise covered by a region or object callback.
|
Tracker |
setNotification(long objId,
long regionId,
Tracker.NotificationType type)
Subscribe to notifications about a particular object/region pair
|
Tracker |
setNotification(TrackerMsg msg)
Add a single subscription to the database
|
void |
setNotificationQueueSize(int maxSize)
The number of notifications without callbacks to store before discarding the oldest
|
Tracker |
setNotifications(java.util.List<TrackerMsg> subscriptionList)
Set multiple subscriptions on the server.
|
void |
setNumLocationQueues(int numLocationQueues)
Set the number of location queues we should use when creating a new tracking set on the database.
|
void |
setNumTrackingQueues(int numTrackingQueues)
Set the number of tracking queues we should use when creating a new tracking set on the database.
|
void |
setObjectCallback(long objectId,
Tracker.Callback<NotificationMsg> callback)
When a notification occurs for the given objectId, forward the notification
to the specified callback method.
|
void |
setRegionCallback(long regionId,
Tracker.Callback<NotificationMsg> callback)
When a notification occurs for the given regionId, forward the notification
to the specified callback method.
|
Tracker |
setTrackingRegion(long regionId,
JGeometry geom)
Set a tracking region on the server.
|
Tracker |
startTrackingSet(boolean createIfNecessary)
Connect to an existing tracking set on the database; optionally create if it doesn't already exist.
|
Tracker |
stopDrop()
Stop (if running) and drop the tracker on the database, cleaning up all database resources.
|
Tracker |
stopTrackingSet()
Stop the tracker on the database.
|
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.
|
public Tracker(java.lang.String owner, java.lang.String name, OracleDataSource ods) throws java.sql.SQLException, JMSException
This object must only be used by a single thread. use Tracker(Tracker)
to duplicate this for use in a separate thread.
owner
- the owner (schema) of the queuename
- the name of this trackerods
- Oracle data sourcejava.sql.SQLException
- for problems communicating with the databaseJMSException
- from databasepublic Tracker newSession() throws JMSException, java.sql.SQLException
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.
JMSException
- on database errorjava.sql.SQLException
- on database errorpublic java.lang.Thread createWorkerThread(Tracker.WorkerType workerType, java.util.function.BooleanSupplier exitFlag) throws JMSException, java.sql.SQLException
Caller is responsible for calling Thread.start() on the returned object.
To terminate thread, arrange for exitFlag to return true and interrupt the thread.
workerType
- the type of worker to createexitFlag
- worker will run until this lambda returns true (typically refers to a volatile boolean)JMSException
- on database errorjava.sql.SQLException
- on database errorpublic void setNumLocationQueues(int numLocationQueues)
numLocationQueues
- the number of queues to createpublic void setNumTrackingQueues(int numTrackingQueues)
numTrackingQueues
- the number of queues to createpublic Tracker allowRegionCallbacks(boolean allowed)
allowed
- set true to allow per-region callbacks to be setpublic void setNotificationQueueSize(int maxSize)
maxSize
- maximum number of unprocessed notifications to store; older ones will be dropped to make room for new.public Tracker allowObjectCallbacks(boolean allowed)
allowed
- true to allow per-object callbacks to be setpublic static void debugOutput(java.io.PrintStream ps)
Example usage
Tracker.debugOutput(Sysetm.err);
Tracker.debugLevel(3);
ps
- the print stream to log topublic static void debugLevel(int level)
level
- Zero (most detailed) through 9 (fatal errors only). Three is a good starting point.public Tracker stopTrackingSet() throws java.sql.SQLException
java.sql.SQLException
- from database operationpublic Tracker startTrackingSet(boolean createIfNecessary) throws java.sql.SQLException, JMSException
createIfNecessary
- if true and tracking set doesn't exist on database, create onejava.sql.SQLException
- from database operationJMSException
- from database operationpublic Tracker begin() throws java.sql.SQLException, JMSException
java.sql.SQLException
- from database operationJMSException
- from database operationpublic Tracker stopDrop() throws java.sql.SQLException
java.sql.SQLException
- from database operationpublic Tracker createTrackingSet() throws java.sql.SQLException
startTrackingSet(boolean)
.
It is an error if the tracker already exists.java.sql.SQLException
- from database operationpublic Tracker setTrackingRegion(long regionId, JGeometry geom) throws java.sql.SQLException, JMSException
regionId
- an integer identifying the regiongeom
- the corresponding geometry (polygon or multipolygon)java.sql.SQLException
- on database errorJMSException
- on database errorpublic Tracker deleteTrackingRegion(long regionId, boolean deleteNotifications) throws java.sql.SQLException, JMSException
regionId
- the ID of the region to removedeleteNotifications
- if true, subscriptions matching the regionId are also deletedjava.sql.SQLException
- on database errorJMSException
- on database errorpublic Tracker deleteObjectSubscription(long objectId, java.lang.Long regionId) throws java.sql.SQLException, JMSException
objectId
- the ID of the object to no longer trackregionId
- the ID of a particular region, or null for all regionsjava.sql.SQLException
- on database errorJMSException
- on database errorpublic void location(long objId, double x, double y, java.sql.Timestamp timestamp) throws java.sql.SQLException, JMSException
createWorkerThread(WorkerType.LOCATION_WORKER, flag).start()
.objId
- the object's IDx
- the x-value of the locationy
- the y-value of the locationtimestamp
- the timestamp of this location messagejava.sql.SQLException
- from databaseJMSException
- from databasepublic void location(long objId, double x, double y) throws java.sql.SQLException, JMSException
location(objId, x, y, new Timestamp(System.currentTimeMillis())
.
See description there.objId
- the object's IDx
- the x-value of the locationy
- the y-value of the locationjava.sql.SQLException
- from databaseJMSException
- from databasepublic void sendLocations() throws JMSException, java.sql.SQLException
JMSException
- from database operationjava.sql.SQLException
- from database operationpublic Tracker setNotification(long objId, long regionId, Tracker.NotificationType type) throws java.sql.SQLException, JMSException
objId
- an integer identifying the object to be notified aboutregionId
- an integer identifying the regiontype
- INSIDE/OUTSIDE/TRANSITIONjava.sql.SQLException
- on database errorJMSException
- on database errorpublic Tracker setNotification(TrackerMsg msg) throws java.sql.SQLException, JMSException
msg
- the subscriptionjava.sql.SQLException
- on database errorJMSException
- on database errorpublic Tracker setNotifications(java.util.List<TrackerMsg> subscriptionList) throws java.sql.SQLException, JMSException
subscriptionList
- the list of subscriptionsjava.sql.SQLException
- on database errorJMSException
- on database errorpublic void setRegionCallback(long regionId, Tracker.Callback<NotificationMsg> callback)
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)
regionId
- the region we are interested incallback
- the method to call when we have a notification for regionId, or null to delete any existing callback for regionIdpublic void setObjectCallback(long objectId, Tracker.Callback<NotificationMsg> callback)
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)
objectId
- the object we are interested incallback
- the method to call when we have a notification for objectId, or null to remove any existing callback for objectIdpublic void setCallback(Tracker.Callback<NotificationMsg> 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.
callback
- the callback to execute, or null to remove an existing callbackpublic java.util.List<NotificationMsg> getNotifications(long waitMs) throws JMSException, java.sql.SQLException
Arriving notifications that match callbacks will be processed via those callbacks during this call and not returned.
waitMs
- number of milliseconds to wait if none currently available: -1 means no wait, zero means infinite waitjava.sql.SQLException
- propagated from databaseJMSException
- propagated from databasepublic NotificationMsg getNotification(long waitMs) throws JMSException, java.sql.SQLException
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.
waitMs
- length of time to wait (-1 = don't wait, 0 = wait forever)JMSException
- from database operationjava.sql.SQLException
- from database operationpublic NotificationMsg getNotification() throws JMSException, java.sql.SQLException
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.
JMSException
- from database operationjava.sql.SQLException
- from database operationpublic int fetchNotifications(long waitMs) throws JMSException, java.sql.SQLException
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.
waitMs
- length of time to wait (-1 = don't wait, 0 = wait forever)JMSException
- from database operationjava.sql.SQLException
- from database operationpublic void close() throws JMSException, java.sql.SQLException
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.
JMSException
- on database errorjava.sql.SQLException
- on database errorpublic void finalize() throws java.lang.Throwable
finalize
in class java.lang.Object
java.lang.Throwable
public java.util.Map<java.lang.String,java.lang.Long> getStatistics()
The values reported and the labels given may change without notice. These are intended as a human-readable debugging aid.
maxQueuedLocations | The high-water mark of locations waiting transmission to server |
notificationQueue current size | Current number of notifications waiting for the user to read (excludes anything that matches a callback) |
notificationQueue maxQueueSizeSeen | High-water mark of the notification queue |
notificationQueue numBatchLargerThanSizeLimit | Number of times a single insertion with more than sizeLimit notifications was attempted |
notificationQueue numQueued | Number of notifications inserted into the queue |
notificationQueue numRemoved | Number of notifications retrieved by the user |
notificationQueue numSurplus | Number of notifications discarded due to reaching sizeLimit |
notificationQueue sizeLimit | Maximum number of notifications stored before discarding oldest |
numGeneralCallbacks (approx) | Number of notifications that went through the general callback instead of being queued |
numLocations | Number of location calls made |
numMaxBatches | Locations 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 |
unsentLocations | Locations not yet sent to the server |
public java.lang.String getOwner()
public java.lang.String getName()
public boolean trackingsetExists()
public int getNotificationQueueSize()
public int getNumLocationQueues()
public int getNumTrackingQueues()