All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendof.datatransfer.snapshot.SnapshotSubscriber Maven / Gradle / Ivy

Go to download

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