org.opendof.datatransfer.snapshot.SnapshotSubscriber Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dof-data-transfer-snapshot Show documentation
Show all versions of dof-data-transfer-snapshot Show documentation
The Java Data Transfer Snapshot Library provides an API for developers to send and receive snapshots of data using OpenDOF protocols.
The newest version!
package org.opendof.datatransfer.snapshot;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opendof.core.oal.DOF;
import org.opendof.core.oal.DOFErrorException;
import org.opendof.core.oal.DOFException;
import org.opendof.core.oal.DOFInterestLevel;
import org.opendof.core.oal.DOFInterfaceID;
import org.opendof.core.oal.DOFObject;
import org.opendof.core.oal.DOFObjectID;
import org.opendof.core.oal.DOFOperation;
import org.opendof.core.oal.DOFOperation.Query;
import org.opendof.core.oal.DOFQuery;
import org.opendof.core.oal.DOFSubscription;
import org.opendof.core.oal.DOFSubscription.State;
import org.opendof.core.oal.DOFSystem;
import org.opendof.core.oal.DOFSystem.InterestOperationListener;
import org.opendof.core.oal.DOFSystem.QueryOperationListener;
import org.opendof.core.oal.DOFValue;
import org.opendof.core.oal.value.DOFBlob;
import org.opendof.datatransfer.ValueSet;
import org.opendof.datatransfer.ValueSet.Definition;
import org.opendof.datatransfer.internal.AttributeUtil;
import org.opendof.datatransfer.internal.DataSinkInterface;
import org.opendof.datatransfer.internal.DataSnapshotInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class allows an application to become a subscriber of snapshot information for objects.
*
* The SLF4J library is used for all non-OpenDOF logging. If logging of the OpenDOF OAL library is desired, a logger must
* be added to the OAL by calling {@link DOF.Log#addListener}.
*/
public class SnapshotSubscriber {
private static final int DEFAULT_THREAD_COUNT = 5;
private static final int MAX_SUBSCRIPTION_PERIOD = 32767;
private static final int MIN_TIMEOUT = 10000;
private static final int MAX_TIMEOUT = 60000;
private static final int GET_RETRY_PERIOD = 15000;
private final DOFSystem system;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ArrayList snapshotListeners = new ArrayList();
private final Hashtable snapshotRequestors = new Hashtable();
private final ScheduledThreadPoolExecutor threadpool;
private boolean isShutdown = false;
/**
* Terminate and close this instance.
*/
public void close() {
if(isShutdown) return;
isShutdown = true;
synchronized(snapshotRequestors){
for(SnapshotRequestor snapshotRequestor: snapshotRequestors.values()){
snapshotRequestor.close();
}
snapshotRequestors.clear();
}
synchronized(snapshotListeners){
for(SnapshotListener listener : snapshotListeners){
try{
listener.removed(this);
} catch (Throwable t){
logger.warn("Unhandled exception thrown in callback method SnapshotListener.remove(): " + t, t);
}
}
snapshotListeners.clear();
}
if(threadpool != null) threadpool.shutdownNow();
}
/**
* The DOFSystem instance allows this component to use OpenDOF to facilitate network communications.
* @param system The DOFSystem instance allowing this class to use OpenDOF to facilitate network communications. Must not be null.
*/
public SnapshotSubscriber(DOFSystem system) {
this.system = system;
threadpool = new ScheduledThreadPoolExecutor(DEFAULT_THREAD_COUNT);
}
/**
* Begins snapshot collection for a specified object and interface with a minimum update frequency.
* @param oid The object for which a snapshot is desired. Must not be null.
* @param iid The interface for which a snapshot is desired. Must not be null.
* @param minPeriod The minimum period at which updates should be received. Must not be negative.
* Updates will be received as determined by the provider (whether faster or slower), but this provides a way to indicate the desired frequency.
*
* @throws DuplicateSubscribeException Thrown if a subscription already exists with an identical oid and iid.
*/
public void start(DOFObjectID oid, DOFInterfaceID iid, int minPeriod) throws DuplicateSubscribeException {
if(oid == null) throw new IllegalArgumentException("oid == null");
if(iid == null) throw new IllegalArgumentException("iid == null");
if(minPeriod < 0) throw new IllegalArgumentException("minPeriod < 0");
if(isShutdown) throw new IllegalStateException("SnapshotSubscriber has been terminated.");
DOFObjectID requestorID = AttributeUtil.getIDWithInterfaceAttribute(oid, iid);
synchronized(snapshotRequestors){
if(snapshotRequestors.containsKey(requestorID))
throw new DuplicateSubscribeException();
SnapshotRequestor snapshotRequestor = null;
if(minPeriod <= MAX_SUBSCRIPTION_PERIOD){
snapshotRequestor = new DataSnapshotSubscriptionRequestor(this, requestorID, minPeriod);
}
else{
snapshotRequestor = new DataSnapshotGetRequestor(this, requestorID, minPeriod);
}
snapshotRequestors.put(requestorID, snapshotRequestor);
snapshotRequestor.activate();
}
}
// /**
// * Begins snapshot collection for a specified object and definition with a minimum update frequency.
// * @param oid The object for which a snapshot is desired. The object must contain a session attribute. Must not be null.
// * @param definition The definition of the snapshot that is desired. Must not be null.
// * @param minPeriod The minimum period at which updates should be received. Must not be negative.
// * Updates will be received as determined by the provider (whether faster or slower), but this provides a way to indicate the desired frequency.
// */
// public void start(DOFObjectID oid, ValueSet.Definition definition, int minPeriod) throws DuplicateSubscribeException {
// }
/**
* Stops collecting snapshot data for the specified object and interface.
* @param oid The object for which a snapshot is no longer desired. Must not be null.
* @param iid The interface for which a snapshot is no longer desired. Must not be null.
*/
public void stop(DOFObjectID oid, DOFInterfaceID iid) {
if(oid == null) throw new IllegalArgumentException("oid == null");
if(iid == null) throw new IllegalArgumentException("iid == null");
if(isShutdown) throw new IllegalStateException("SnapshotSubscriber has been terminated.");
DOFObjectID requestorID = AttributeUtil.getIDWithInterfaceAttribute(oid, iid);
SnapshotRequestor snapshotRequestor = null;
synchronized(snapshotRequestors){
if(!snapshotRequestors.containsKey(requestorID)){
return;
}
snapshotRequestor = snapshotRequestors.remove(requestorID);
}
snapshotRequestor.close();
}
// /**
// * Stops collecting snapshot data for the specified object and definition.
// * @param oid The object for which a snapshot is no longer desired. The object must contain a session attribute. Must not be null.
// * @param definition The definition of the snapshot that is no longer desired. Must not be null.
// */
// public void stop(DOFObjectID oid, ValueSet.Definition definition) {
//
// }
/**
* Adds a listener, which will receive snapshotReceived events as they arrive. Adding this listener does not cause any immediate callback with the last snapshots received.
* @param listener The listener to be added.
*/
public void addListener(SnapshotListener listener) {
if(listener == null) throw new IllegalArgumentException("listener == null");
synchronized(snapshotListeners){ snapshotListeners.add(listener); }
}
/**
* Removes a previously added listener.
* @param listener The listener to be removed.
*/
public void removeListener(SnapshotListener listener) {
if(listener == null) throw new IllegalArgumentException("listener == null");
synchronized(snapshotListeners){ snapshotListeners.remove(listener); }
try{
listener.removed(this);
} catch (Throwable t){
logger.debug("Exception thrown in callback method SnapshotListener.remove(): " + t, t);
}
}
private void valueChanged(DOFObjectID objectID, ValueSet.Row snapshot){
DOFObjectID oid = AttributeUtil.getIDWithoutInterfaceAttribute(objectID);
DOFInterfaceID iid = AttributeUtil.getIIDFromObjectAttribute(objectID);
synchronized(snapshotListeners){
for(SnapshotListener listener : snapshotListeners){
try{
listener.snapshotReceived(this, oid, iid, snapshot);
} catch (Throwable t){
logger.debug("Exception thrown in callback method snapshotReceived(): " + t, t);
}
}
}
}
private void snapshotProviderAdded(DOFObjectID objectID, ValueSet.Definition definition){
logger.trace("snapshotProviderAdded: {}", objectID);
DOFObjectID oid = AttributeUtil.getIDWithoutInterfaceAttribute(objectID);
DOFInterfaceID iid = AttributeUtil.getIIDFromObjectAttribute(objectID);
synchronized(snapshotListeners){
for(SnapshotListener listener : snapshotListeners){
try{
listener.snapshotProviderAdded(this, oid, iid, definition);
} catch (Throwable t){
logger.debug("Exception thrown in callback method snapshotProviderAdded(): " + t, t);
}
}
}
}
private void snapshotProviderRemoved(DOFObjectID objectID){
logger.trace("snapshotProviderRemoved: {}", objectID);
DOFObjectID oid = AttributeUtil.getIDWithoutInterfaceAttribute(objectID);
DOFInterfaceID iid = AttributeUtil.getIIDFromObjectAttribute(objectID);
synchronized(snapshotListeners){
for(SnapshotListener listener : snapshotListeners){
try{
listener.snapshotProviderRemoved(this, oid, iid);
} catch (Throwable t){
logger.debug("Exception thrown in callback method snapshotProviderRemoved(): " + t, t);
}
}
}
}
/**
* Exception indicating that a subscribe has already been started for the specified DOFObjectID and DOFInterfaceID combination.
*
*/
public static class DuplicateSubscribeException extends Exception {
/**
* Unique Serial Version
*/
private static final long serialVersionUID = 8274110711831709420L;
}
private interface SnapshotRequestor{
public abstract void activate();
public abstract void close();
public abstract void addSnapshotProvider(DOFObjectID objectID, ValueSet.Definition definition);
}
private static class DataSnapshotGetRequestor implements SnapshotRequestor, QueryOperationListener, InterestOperationListener, Runnable {
private final DOFObjectID requestorID;
private final DOFSystem system;
private final SnapshotSubscriber subscriber;
private final int period;
private ValueSet.Definition definition;
private int timeout;
private DOFObject requestor;
private DOFOperation.Interest interestOp;
private DOFOperation.Query queryOp;
private AtomicBoolean isClosed = new AtomicBoolean(false);
private long nextScheduleTime = 0;
private final AtomicBoolean isScheduled = new AtomicBoolean(false);
private final AtomicBoolean isConnected = new AtomicBoolean(false);
private final AtomicBoolean isProviderAdded = new AtomicBoolean(false);
private final Object scheduleMonitor = new Object();
private final Object providerMonitor = new Object();
private ValueSet.Row lastReportedSnapshot;
private GetDefinitionTask getDefinitionTask;
public DataSnapshotGetRequestor(SnapshotSubscriber subscriber, DOFObjectID requestorID, int period){
this.subscriber = subscriber;
this.system = subscriber.system;
this.requestorID = requestorID;
this.period = period;
this.timeout = Math.max(MIN_TIMEOUT, period);
this.timeout = Math.min(MAX_TIMEOUT, timeout);
}
@Override
public void activate(){
interestOp = system.beginInterest(requestorID, DataSnapshotInterface.InterfaceID, DOFInterestLevel.ACTIVATE, DOF.TIMEOUT_NEVER, null, null);
DOFQuery query = new DOFQuery.Builder()
.addFilter(requestorID)
.addRestriction(DataSnapshotInterface.InterfaceID)
.build();
queryOp = system.beginQuery(query, DOF.TIMEOUT_NEVER, this, null);
}
@Override
public void close(){
isClosed.set(true);
if(queryOp != null) queryOp.cancel();
if(interestOp != null) interestOp.cancel();
if(getDefinitionTask != null) getDefinitionTask.cancel();
if(requestor != null) requestor.destroy();
}
@Override
public void addSnapshotProvider(DOFObjectID objectID, ValueSet.Definition definition) {
synchronized(providerMonitor){
if(!isConnected.get()){
return;
}
this.definition = definition;
isProviderAdded.set(true); //Must share sync block with isConnected.get();
}
subscriber.snapshotProviderAdded(objectID, definition);
if(!isScheduled.get()){
schedule();
}
}
@Override
public void complete(DOFOperation operation, DOFException exception){
if(isClosed.get()) return;
if(operation == interestOp){
subscriber.logger.debug("Lost interest to " + requestorID + (exception == null ? "" : " - " + exception));
}
else if(operation == queryOp){
subscriber.logger.debug("Lost query to " + requestorID + (exception == null ? "" : " - " + exception));
}
}
@Override
public void interfaceAdded(Query operation, DOFObjectID objectID, DOFInterfaceID interfaceID){
subscriber.logger.trace("interfaceAdded -- {} -- {}", objectID, interfaceID);
isConnected.set(true);
if(requestor == null){
requestor = system.createObject(objectID);
}
getDefinitionTask = new GetDefinitionTask(subscriber, requestor, this, timeout);
new Thread(getDefinitionTask).start();
}
@Override
public void interfaceRemoved(Query operation, DOFObjectID objectID, DOFInterfaceID interfaceID){
subscriber.logger.trace("interfaceRemoved -- {} -- {}", objectID, interfaceID);
boolean shouldCallProviderRemoved = false;
synchronized(providerMonitor){
isConnected.set(false);
if(isProviderAdded.get()){
isProviderAdded.set(false); //Must share sync block with isConnected.set(false);
shouldCallProviderRemoved = true;
}
}
if(shouldCallProviderRemoved){
subscriber.snapshotProviderRemoved(objectID);
}
if(getDefinitionTask != null){
getDefinitionTask.cancel();
}
subscriber.snapshotProviderRemoved(objectID);
}
@Override
public void providerRemoved(Query operation, DOFObjectID objectID){
subscriber.logger.trace("providerRemoved -- {}", objectID);
}
@Override
public void run() {
isScheduled.set(false);
if(!isConnected.get() || isClosed.get()){
return;
}
calculateNextScheduleTime();
ValueSet.Row snapshot = getSnapshot();
if(snapshot != null){
if(lastReportedSnapshot == null || !lastReportedSnapshot.equals(snapshot)){
subscriber.valueChanged(requestor.getObjectID(), snapshot);
lastReportedSnapshot = snapshot;
}
}
schedule();
}
public ValueSet.Row getSnapshot(){
DOFValue value = null;
try{
DOFOperation.Control control = new DOFOperation.Control();
control.setRetryPeriod(GET_RETRY_PERIOD);
value = requestor.get(DataSnapshotInterface.DEF.getProperty(DataSnapshotInterface.SnapshotItem), control, timeout).get();
}
catch(Exception e){
subscriber.logger.debug("Failed to get snapshot from {} -- {}", requestor.getObjectID(), e);
return null;
}
ValueSet.Row snapshot = null;
try{
snapshot = new ValueSet.Row.Builder(definition, DataSnapshotInterface.InterfaceID, value).build();
} catch (Exception e){
subscriber.logger.warn("Failed to create snapshot from " + requestor.getObjectID() + " -- " + e, e);
}
return snapshot;
}
private void schedule(){
synchronized(scheduleMonitor){
if(!isClosed.get()){
isScheduled.set(true);
subscriber.threadpool.schedule(this, getScheduleDelay(), TimeUnit.MILLISECONDS);
}
}
}
private void calculateNextScheduleTime(){
if(nextScheduleTime == 0){
nextScheduleTime = System.currentTimeMillis();
}
nextScheduleTime += period;
}
private long getScheduleDelay(){
long delay = nextScheduleTime - System.currentTimeMillis();
if(delay < 0){
delay = 0;
}
return delay;
}
}
private static class DataSnapshotSubscriptionRequestor implements SnapshotRequestor, DOFSubscription.Listener, QueryOperationListener, InterestOperationListener {
//private static final int MIN_RETRY_DELAY = 1000;
//private static final int MAX_RETRY_DELAY = 2 * 60 * 1000;
private static final int SUBSCRIBE_RETRY_PERIOD = 5 * 60 * 1000;
private final DOFObjectID requestorID;
private final DOFSystem system;
private final SnapshotSubscriber subscriber;
private final int minPeriod;
private ValueSet.Definition definition;
private int timeout;
private DOFObject requestor;
private DOFOperation.Interest interestOp;
private DOFOperation.Query queryOp;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
private final AtomicBoolean isConnected = new AtomicBoolean(false);
private final AtomicBoolean isProviderAdded = new AtomicBoolean(false);
private final Object providerMonitor = new Object();
private GetDefinitionTask getDefinitionTask;
private DOFSubscription subscription;
public DataSnapshotSubscriptionRequestor(SnapshotSubscriber subscriber, DOFObjectID requestorID, int minPeriod){
this.subscriber = subscriber;
this.system = subscriber.system;
this.requestorID = requestorID;
this.minPeriod = minPeriod;
this.timeout = Math.max(MIN_TIMEOUT, minPeriod);
this.timeout = Math.min(MAX_TIMEOUT, timeout);
}
@Override
public void activate(){
interestOp = system.beginInterest(requestorID, DataSnapshotInterface.InterfaceID, DOFInterestLevel.ACTIVATE, DOF.TIMEOUT_NEVER, null, null);
DOFQuery query = new DOFQuery.Builder()
.addFilter(requestorID)
.addRestriction(DataSnapshotInterface.InterfaceID)
.build();
queryOp = system.beginQuery(query, DOF.TIMEOUT_NEVER, this, null);
}
@Override
public void close(){
isClosed.set(true);
if(queryOp != null) queryOp.cancel();
if(interestOp != null) interestOp.cancel();
if(getDefinitionTask != null) getDefinitionTask.cancel();
if(requestor != null) requestor.destroy();
if(subscription != null) subscription.destroy();
}
@Override
public void addSnapshotProvider(DOFObjectID objectID, ValueSet.Definition definition) {
synchronized(providerMonitor){
if(!isConnected.get()){
return;
}
this.definition = definition;
isProviderAdded.set(true);//Must share sync block with isConnected.get();
}
subscriber.snapshotProviderAdded(objectID, definition);
subscribe();
}
private void subscribe(){
DOFOperation.Control control = new DOFOperation.Control();
control.setRetryPeriod(SUBSCRIBE_RETRY_PERIOD);
try {
if(subscription == null){
subscription = system.createSubscription(queryOp.getQuery(), DataSnapshotInterface.DEF.getProperty(DataSnapshotInterface.SnapshotItem), minPeriod, control, this, null);
}
} catch (DOFErrorException e) {
subscriber.logger.error("Subscription not valid: " + e); //Should never happen. Probably a bug with Snapshot or DOFSubscription if it does.
}
}
@Override
public void complete(DOFOperation operation, DOFException exception){
if(isClosed.get()) return;
if(operation == interestOp){
subscriber.logger.debug("Lost interest to {}{}", requestorID, (exception == null ? "" : " - " + exception));
}
else if(operation == queryOp){
subscriber.logger.debug("Lost query to {}{}", requestorID, (exception == null ? "" : " - " + exception));
}
}
@Override
public void propertyChanged(DOFSubscription subscription, DOFObjectID providerID, DOFValue value) {
ValueSet.Row snapshot = null;
try{
snapshot = new ValueSet.Row.Builder(definition, DataSnapshotInterface.InterfaceID, value).build();
} catch (Exception e){
subscriber.logger.warn("Failed to create snapshot from " + requestor.getObjectID() + " -- " + e, e);
}
subscriber.valueChanged(providerID, snapshot);
}
@Override
public void stateChanged(DOFSubscription subscription, State state) {
subscriber.logger.debug("Snapshot subscription to {} state changed. isActive={} exception={}", requestorID, state.isActive(), state.getException());
}
@Override
public void removed(DOFSubscription subscription, DOFException exception) {
if(!isClosed.get()){
subscriber.logger.debug("Snapshot subscription to {} closed unexpectedly.", requestorID);
}
this.subscription = null;
}
@Override
public void interfaceAdded(Query operation, DOFObjectID objectID, DOFInterfaceID interfaceID){
subscriber.logger.trace("interfaceAdded -- {} -- {}", objectID, interfaceID);
isConnected.set(true);
if(requestor == null){
requestor = system.createObject(objectID);
}
getDefinitionTask = new GetDefinitionTask(subscriber, requestor, this, timeout);
new Thread(getDefinitionTask).start();
}
@Override
public void interfaceRemoved(Query operation, DOFObjectID objectID, DOFInterfaceID interfaceID){
subscriber.logger.trace("interfaceRemoved -- {} -- {}", objectID, interfaceID);
boolean shouldCallProviderRemoved = false;
synchronized(providerMonitor){
isConnected.set(false);
if(isProviderAdded.compareAndSet(true, false)){ //Must share sync block with isConnected.set(false);
shouldCallProviderRemoved = true;
}
}
if(shouldCallProviderRemoved){
subscriber.snapshotProviderRemoved(objectID);
}
if(getDefinitionTask != null){
getDefinitionTask.cancel();
}
}
@Override
public void providerRemoved(Query operation, DOFObjectID objectID){
subscriber.logger.trace("providerRemoved -- {}", objectID);
}
}
private static class GetDefinitionTask implements Runnable{
private static final int RETRY_DELAY_SECONDS = GET_RETRY_PERIOD/1000;
private final SnapshotSubscriber subscriber;
private final DOFObject requestor;
private final SnapshotRequestor snapshotRequestor;
private final int timeout;
private AtomicBoolean isCancelled = new AtomicBoolean(false);
public GetDefinitionTask(SnapshotSubscriber subscriber, DOFObject requestor, SnapshotRequestor snapshotRequestor, int timeout){
this.subscriber = subscriber;
this.requestor = requestor;
this.snapshotRequestor = snapshotRequestor;
this.timeout = timeout;
}
@Override
public void run() {
if(isCancelled.get()){
return;
}
ValueSet.Definition definition = null;
try{
definition = getDefinition();
} catch (Exception e){
subscriber.logger.debug("Failed to get snapshot definition from " + requestor.getObjectID() + " - " + e + ". Retry in " + RETRY_DELAY_SECONDS + " seconds.", e);
schedule();
return;
}
snapshotRequestor.addSnapshotProvider(requestor.getObjectID(), definition);
}
private Definition getDefinition() throws Exception{
DOFOperation.Control control = new DOFOperation.Control();
control.setRetryPeriod(GET_RETRY_PERIOD);
DOFValue result = requestor.get(DataSnapshotInterface.DEF.getProperty(DataSnapshotInterface.DefinitionItem), control, timeout).get();
return new ValueSet.Definition.Builder().loadTransferBytes(DataSinkInterface.InterfaceID, ((DOFBlob)result).get()).build();
}
private void cancel(){
isCancelled.set(true);
}
private void schedule(){
if(!isCancelled.get()){
subscriber.threadpool.schedule(this, RETRY_DELAY_SECONDS, TimeUnit.SECONDS);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy