ca.odell.glazedlists.impl.rbp.PeerResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glazedlists_java15 Show documentation
Show all versions of glazedlists_java15 Show documentation
Event-driven lists for dynamically filtered and sorted tables
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.rbp;
// NIO is used for BRP
import ca.odell.glazedlists.impl.io.Bufferlo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* A resource that is being published on the network.
*
* @author Jesse Wilson
*/
class PeerResource {
/** the peer that owns all connections */
private Peer peer;
/** the resource being managed */
private Resource resource = null;
/** the update ID of the resource */
private int resourceUpdateId = 0;
/** the address that this resource is being published as */
private ResourceUri resourceUri;
/** the publisher of this resource */
private PeerConnection publisher = null;
/** subscribers interested in this resource */
private List subscribers = new ArrayList();
/** the session ID is a simple validation */
private int sessionId = -1;
/** listens to changes in the resource */
private PrivateResourceListener resourceListener = new PrivateResourceListener();
/** provides information about the status of this resource */
private PrivateResourceStatus resourceStatus = new PrivateResourceStatus();
/**
* Create a new PeerResource for an incoming or outgoing resource.
*/
public PeerResource(Peer peer, Resource resource, ResourceUri resourceUri) {
this.peer = peer;
this.resource = resource;
this.resourceUri = resourceUri;
// create a random session ID as a check
if(resourceUri.isLocal()) {
this.sessionId = new Random(System.currentTimeMillis()).nextInt();
}
// subscribe to the resource
resourceStatus.connect();
}
/**
* Gets the address of this resource.
*/
public ResourceUri getResourceUri() {
return resourceUri;
}
/**
* Handle the state of the specified connection changing.
*/
public void connectionClosed(ResourceConnection connection, Exception reason) {
if(publisher == connection.getConnection()) {
publisher = null;
resourceStatus.setConnected(false, reason);
connection.getConnection().incomingSubscriptions.remove(resourceUri);
} else {
subscribers.remove(connection);
connection.getConnection().outgoingPublications.remove(resourceUri);
}
}
/**
* Listens to changes in the resource, so they can be broadcast to subscribers.
*/
private class PrivateResourceListener implements ResourceListener {
/**
* Handles a change in a resource contained by the specified delta. This method
* will be called while holding the Resource's write lock.
*/
public void resourceUpdated(Resource resource, Bufferlo delta) {
resourceUpdateId++;
peer.invokeLater(new UpdatedRunnable(delta, resourceUpdateId));
}
private class UpdatedRunnable implements Runnable {
private Bufferlo delta = null;
private int updateId = -1;
public UpdatedRunnable(Bufferlo delta, int updateId) {
this.delta = delta;
this.updateId = updateId;
}
public void run() {
// if nobody's listening, we're done
if(subscribers.isEmpty()) return;
// forward the event to listeners
PeerBlock block = PeerBlock.update(resourceUri, sessionId, updateId, delta);
// send the block to interested subscribers
for(int s = 0; s < subscribers.size(); s++) {
ResourceConnection subscriber = (ResourceConnection)subscribers.get(s);
if(subscriber.getUpdateId() >= updateId) continue;
subscriber.getConnection().writeBlock(PeerResource.this, block);
subscriber.setUpdateId(updateId);
}
}
}
}
public ResourceListener resourceListener() {
return resourceListener;
}
/**
* Provides information about the status of this resource.
*/
private class PrivateResourceStatus implements ResourceStatus {
/** listeners interested in status changes */
private List statusListeners = new ArrayList();
/** connected if the current subscription has been confirmed */
private boolean connected = false;
/** {@inheritDoc} */
public synchronized boolean isConnected() {
return connected;
}
/** {@inheritDoc} */
public void connect() {
peer.invokeLater(new ConnectRunnable());
}
private class ConnectRunnable implements Runnable {
public void run() {
// if this is remote, subscribe to this resource
if(resourceUri.isRemote()) {
publisher = peer.getConnection(resourceUri.getHost(), resourceUri.getPort());
peer.subscribed.put(resourceUri, PeerResource.this);
publisher.incomingSubscriptions.put(resourceUri, new ResourceConnection(publisher, PeerResource.this));
PeerBlock subscribe = PeerBlock.subscribe(resourceUri);
publisher.writeBlock(PeerResource.this, subscribe);
// if this is local, we're immediately connected
} else if(resourceUri.isLocal()) {
resource.addResourceListener(resourceListener);
resourceStatus.setConnected(true, null);
if(peer.published.get(resourceUri) != null) throw new IllegalStateException();
peer.published.put(resourceUri, PeerResource.this);
}
}
}
/** {@inheritDoc} */
public void disconnect() {
peer.invokeLater(new DisconnectRunnable());
}
private class DisconnectRunnable implements Runnable {
public void run() {
// we're immediately disconnected
resourceStatus.setConnected(false, null);
// if this is remote, unsubscribe
if(resourceUri.isRemote()) {
peer.subscribed.remove(resourceUri);
// clean up the publisher
if(publisher != null) {
publisher.writeBlock(PeerResource.this, PeerBlock.unsubscribe(resourceUri));
publisher.incomingSubscriptions.remove(resourceUri);
if(publisher.isIdle()) publisher.close();
publisher = null;
}
// if this is local, unpublish everyone
} else if(resourceUri.isLocal()) {
resource.removeResourceListener(resourceListener);
if(peer.published.get(resourceUri) == null) throw new IllegalStateException();
peer.published.remove(resourceUri);
// unpublish the subscribers
for(Iterator s = subscribers.iterator(); s.hasNext(); ) {
ResourceConnection subscriber = (ResourceConnection)s.next();
subscriber.getConnection().writeBlock(PeerResource.this, PeerBlock.unpublish(resourceUri));
subscriber.getConnection().outgoingPublications.remove(resourceUri);
if(subscriber.getConnection().isIdle()) subscriber.getConnection().close();
s.remove();
}
}
}
}
/** {@inheritDoc} */
public synchronized void addResourceStatusListener(ResourceStatusListener listener) {
statusListeners.add(listener);
}
/** {@inheritDoc} */
public synchronized void removeResourceStatusListener(ResourceStatusListener listener) {
statusListeners.remove(listener);
}
/**
* Update the status of this PeerResource and notify everyone who's interested.
*/
private void setConnected(boolean connected, Exception reason) {
List listenersToNotify = new ArrayList();
synchronized(PrivateResourceStatus.this) {
this.connected = connected;
listenersToNotify.addAll(statusListeners);
}
for(Iterator i = listenersToNotify.iterator(); i.hasNext(); ) {
ResourceStatusListener statusListener = (ResourceStatusListener)i.next();
if(connected) statusListener.resourceConnected(this);
else statusListener.resourceDisconnected(this, reason);
}
}
}
public ResourceStatus status() {
return resourceStatus;
}
/**
* Handles a block of incoming data.
*/
void incomingBlock(ResourceConnection source, PeerBlock block) {
if(block.isSubscribe()) remoteSubscribe(source, block);
else if(block.isSubscribeConfirm()) remoteSubscribeConfirm(source, block);
else if(block.isUpdate()) remoteUpdate(source, block);
else if(block.isUnsubscribe()) remoteUnsubscribe(source, block);
else if(block.isUnpublish()) remoteUnpublish(source, block);
else throw new IllegalStateException();
}
private void remoteUpdate(ResourceConnection publisher, PeerBlock block) {
// confirm the update is consistent with the session
if(block.getSessionId() != sessionId) throw new IllegalStateException();
// handle the update
resource.getReadWriteLock().writeLock().lock();
try {
// confirm this update is consistent with the update ID
if(block.getUpdateId() != (resourceUpdateId+1)) throw new IllegalStateException("Expected update id " + (resourceUpdateId+1) + " but found " + block.getUpdateId());
// apply locally
resource.update(block.getPayload());
// update state and propagate
resourceListener.resourceUpdated(resource, block.getPayload());
} finally {
resource.getReadWriteLock().writeLock().unlock();
}
}
private void remoteSubscribe(ResourceConnection subscriber, PeerBlock block) {
// we're accepting connections
if(resourceStatus.isConnected()) {
// save the update id and a snapshot
int updateId = -1;
Bufferlo snapshot = null;
resource.getReadWriteLock().writeLock().lock();
try {
updateId = resourceUpdateId;
snapshot = resource.toSnapshot();
} finally {
resource.getReadWriteLock().writeLock().unlock();
}
// create the subscription
subscriber.setUpdateId(updateId);
subscriber.getConnection().outgoingPublications.put(resourceUri, subscriber);
subscribers.add(subscriber);
// now send the snapshot to this subscriber
PeerBlock subscribeConfirm = PeerBlock.subscribeConfirm(resourceUri, sessionId, updateId, snapshot);
subscriber.getConnection().writeBlock(this, subscribeConfirm);
// we're not accepting connections for now
} else {
PeerBlock unpublish = PeerBlock.unpublish(resourceUri);
subscriber.getConnection().writeBlock(this, unpublish);
if(subscriber.getConnection().isIdle()) subscriber.getConnection().close();
}
}
private void remoteSubscribeConfirm(ResourceConnection publisher, PeerBlock block) {
// handle the confirm
resource.getReadWriteLock().writeLock().lock();
try {
// apply locally
resource.fromSnapshot(block.getPayload());
// update state and propagate
resourceUpdateId = block.getUpdateId();
//resourceListener.resourceUpdated(resource, block.getPayload());
} finally {
resource.getReadWriteLock().writeLock().unlock();
}
// save a session cookie to verify this is the same source
sessionId = block.getSessionId();
// finally we're connected
resourceStatus.setConnected(true, null);
}
private void remoteUnsubscribe(ResourceConnection subscriber, PeerBlock block) {
// remove the subscription
subscribers.remove(subscriber);
subscriber.getConnection().outgoingPublications.remove(resourceUri);
}
private void remoteUnpublish(ResourceConnection subscriber, PeerBlock block) {
// immediately disconnected
resourceStatus.setConnected(false, new Exception("Resource became unavailable"));
// clean up the publisher
if(publisher != null) {
publisher.incomingSubscriptions.remove(resourceUri);
if(publisher.isIdle()) publisher.close();
publisher = null;
}
}
/**
* Gets this resource as a String for debugging.
*/
public void print() {
System.out.print(resourceUri);
System.out.print(" from: ");
System.out.print(publisher);
System.out.print(" to: ");
for(Iterator s = subscribers.iterator(); s.hasNext(); ) {
ResourceConnection subscriber = (ResourceConnection)s.next();
System.out.print(subscriber.getConnection().toString());
if(s.hasNext()) System.out.print(", ");
}
System.out.println("");
}
}