//
// ERCNSubscriber.java
// Project ERChangeNotificationJMS
//
// Created by tatsuya on Sat Aug 31 2002
//
package er.changenotification;
import java.util.Enumeration;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import com.webobjects.eoaccess.EODatabase;
import com.webobjects.eoaccess.EODatabaseContext;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOGlobalID;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSLog;
/**
* ERCNSubscriber handles change notifications from other application
* instances via JMS server, and applies the changes to the local EOF stack
* managed by the default EOObjectStoreCoordinator.
* <p>
* There must be exactly one instance of the ERCNSubscriber on the application.
* Its onChange method is called by JMS's own single thread which manages
* asynchronous receiving mechanism, and therefore, it runs concurrently with
* other worker threads and will not interfere request-response cycles.
* <p>
* Note that the subscriber will not receive notifications from itself, but
* from other application instances. This is accomplished by using option flag
* when create the JMS topic subscriber object.
* <p>
* The current implementation only supports updating operations, not inserting
* or deleting operations. You can register a delegate object to process those
* operations.
*/
class ERCNSubscriber implements MessageListener {
private static ERCNSubscriberDelegate _delegate;
private ERCNNotificationCoordinator _coordinator;
private TopicSession _topicSession;
private TopicSubscriber _topicSubscriber;
ERCNSubscriber(ERCNNotificationCoordinator coordinator) {
_coordinator = coordinator;
}
static ERCNSubscriberDelegate delegate() {
return _delegate;
}
static void setDelegate(ERCNSubscriberDelegate delegate) {
_delegate = delegate;
}
void subscribe(TopicConnection connection) {
try {
_topicSession = connection.createTopicSession(false, Session.CLIENT_ACKNOWLEDGE);
} catch (JMSException ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "Failed to create a JMS topic session: " + ex.getMessage());
return;
}
final String selector = null;
final boolean noLocal = true;
try {
if (_coordinator.configuration().isSubscriberDurable()) {
_topicSubscriber = _topicSession.createDurableSubscriber(_coordinator.topic(),
_coordinator.id(), selector, noLocal);
} else {
_topicSubscriber = _topicSession.createSubscriber(_coordinator.topic(), selector, noLocal);
}
} catch (Exception ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "An exception occured: " + ex.getMessage());
}
try {
_topicSubscriber.setMessageListener(this);
} catch (JMSException ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "An exception occured: " + ex.getMessage());
}
}
public void onMessage(Message message) {
try {
ERCNSnapshot snapshot = (ERCNSnapshot) ((ObjectMessage)message).getObject();
if (NSLog.debug.isEnabled())
NSLog.debug.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "Received a message with snapshot: " + snapshot);
if (ERCNSnapshot.shouldApplyChangeFor(ERCNSnapshot.INSERTED))
_processInsertions(snapshot);
if (ERCNSnapshot.shouldApplyChangeFor(ERCNSnapshot.DELETED))
_processDeletions(snapshot);
if (ERCNSnapshot.shouldApplyChangeFor(ERCNSnapshot.UPDATED))
_processUpdates(snapshot);
NSLog.debug.appendln(ERCNNotificationCoordinator.LOG_HEADER + "Finished processing changes.");
} catch (JMSException ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "An exception occured: " + ex.getMessage());
} finally {
try {
message.acknowledge();
} catch (JMSException ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "An exception occured: " + ex.getMessage());
}
}
}
void unsubscribe() {
try {
if (_coordinator.configuration().isSubscriberDurable()) {
_topicSession.unsubscribe(_coordinator.id());
}
} catch (JMSException ex) {
NSLog.err.appendln(ERCNNotificationCoordinator.LOG_HEADER
+ "An exception occured: " + ex.getMessage());
}
_topicSession = null;
}
private void _processInsertions(ERCNSnapshot ercnSnapshot) {
if (_delegate != null)
_delegate.processInsertions(ercnSnapshot);
}
private void _processDeletions(ERCNSnapshot ercnSnapshot) {
if (_delegate != null)
_delegate.processDeletions(ercnSnapshot);
}
private void _processUpdates(ERCNSnapshot ercnSnapshot) {
if (_delegate != null) {
boolean isProcessed = _delegate.processUpdates(ercnSnapshot);
if (isProcessed)
return;
}
NSArray entityNames = ercnSnapshot.shapshotsForUpdateGroupedByEntity().allKeys();
EOEditingContext ec = new EOEditingContext();
ec.lock();
Enumeration entitiesEnumerator = entityNames.objectEnumerator();
while (entitiesEnumerator.hasMoreElements()) {
String entityName = (String)entitiesEnumerator.nextElement();
if (! ERCNSnapshot.shouldSynchronizeEntity(entityName)) continue;
EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName);
EODatabaseContext dbContext = ERCNNotificationCoordinator.databaseContextForEntityNamed(entityName, ec);
EODatabase database = dbContext.database();
NSArray snapshots = (NSArray)ercnSnapshot.shapshotsForUpdateGroupedByEntity().objectForKey(entityName);
Enumeration snapshotsEnumerator = snapshots.objectEnumerator();
while (snapshotsEnumerator.hasMoreElements()) {
NSDictionary snapshot = (NSDictionary)snapshotsEnumerator.nextElement();
if (NSLog.debug.isEnabled())
NSLog.debug.appendln(ERCNNotificationCoordinator.LOG_HEADER + "Snapshot: " + snapshot);
if (snapshot != null) {
EOGlobalID globalID = entity.globalIDForRow(snapshot);
dbContext.lock();
database.forgetSnapshotForGlobalID(globalID);
database.recordSnapshotForGlobalID(snapshot, globalID);
dbContext.unlock();
}
}
}
ec.unlock();
}
// ENHANCEME: The subscriber could be processing the last destributed notification
// when it's requested to terminate. Should wait *here* for the last
// notification to finish, then return so that the coordinator can safely
// close the connection to the JMS server.
synchronized void terminate() {
unsubscribe();
}
}