/****************************************************************************
* Copyright (c) 2004 Composent, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Composent, Inc. - initial API and implementation
*****************************************************************************/
package org.eclipse.ecf.example.collab.share;
import java.util.Hashtable;
import java.util.Vector;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.sharedobject.ISharedObjectContainerTransaction;
import org.eclipse.ecf.core.sharedobject.SharedObjectAddAbortException;
import org.eclipse.ecf.internal.example.collab.Messages;
public class TransactionSharedObject extends GenericSharedObject implements
ISharedObjectContainerTransaction {
public static final String REPLICA_COMMIT_MSG = "replicaCommit"; //$NON-NLS-1$
public static int DEFAULT_TIMEOUT = 30000;
// Dummy inner class to provide lock
static final class Lock {
}
// Timeout value associated with this object's replication
protected int timeout;
// Replication state this object is currently in.
protected byte state;
// A lock variable
protected Lock lock;
protected Vector participantIDs;
protected Hashtable failedParticipants;
public TransactionSharedObject(int timeout) {
this.timeout = timeout;
init();
}
public TransactionSharedObject() {
this(DEFAULT_TIMEOUT);
}
protected void init() {
state = ISharedObjectContainerTransaction.ACTIVE;
lock = new Lock();
participantIDs = new Vector();
failedParticipants = new Hashtable();
}
public void activated(ID[] others) {
// No other state changes while this is going on
synchronized (lock) {
if (isHost()) {
replicate(null);
addRemoteParticipants(getContext().getGroupMemberIDs());
state = ISharedObjectContainerTransaction.VOTING;
// Clients
} else {
try {
// Try to respond with create success message back to host
getContext().sendCreateResponse(getHomeContainerID(), null,
getNextReplicateID());
// If above succeeds, we're now in prepared state
state = ISharedObjectContainerTransaction.PREPARED;
} catch (Exception e) {
// If throws exception, we're doomed
state = ISharedObjectContainerTransaction.ABORTED;
log("unable to send create response to " //$NON-NLS-1$
+ getHomeContainerID(), e);
}
}
// Notify any threads waiting on state change
lock.notifyAll();
}
}
public void memberAdded(ID member) {
if (isHost()) {
// If we are currently in VOTING state, then add the new member to
// list of participants
// and send replicate message. If not in voting state, just send
// replicate message
synchronized (lock) {
replicate(member);
if (getTransactionState() == ISharedObjectContainerTransaction.VOTING)
addRemoteParticipants(new ID[] { member });
else
replicate(member);
}
}
}
protected void addRemoteParticipants(ID ids[]) {
if (ids != null && participantIDs != null) {
for (int i = 0; i < ids.length; i++) {
if (!getHomeContainerID().equals(ids[i]))
participantIDs.addElement(ids[i]);
}
}
}
protected void removeRemoteParticipant(ID id) {
if (id != null && participantIDs != null) {
int index = participantIDs.indexOf(id);
if (index != -1)
participantIDs.removeElementAt(index);
}
}
protected void addRemoteParticipantFailed(ID remote, Throwable failure) {
if (remote != null && failure != null && failedParticipants != null) {
failedParticipants.put(remote, failure);
}
}
public void handleCreateResponse(ID fromID, Throwable e, Long identifier) {
// If no exception, remove
synchronized (lock) {
if (state == ISharedObjectContainerTransaction.VOTING) {
if (e == null) {
removeRemoteParticipant(fromID);
} else {
addRemoteParticipantFailed(fromID, e);
}
} else {
handleVotingCompletedCreateResponse(fromID, e, identifier);
}
lock.notifyAll();
}
}
protected void handleVotingCompletedCreateResponse(ID fromID, Throwable e,
Long identifier) {
// If remote creation was successful, simply send commit message back.
if (e == null) {
// send commit message right back.
try {
forwardMsgTo(fromID, SharedObjectMsg.createMsg((String) null,
REPLICA_COMMIT_MSG));
} catch (Exception except) {
log("Exception sending commit message to " + fromID, except); //$NON-NLS-1$
}
}
}
public void memberRemoved(ID member) {
// We only care about this if we are the host.
if (isHost()) {
synchronized (lock) {
if (state == ISharedObjectContainerTransaction.VOTING) {
addRemoteParticipantFailed(member, new Exception("Member " //$NON-NLS-1$
+ member + " left")); //$NON-NLS-1$
}
lock.notifyAll();
}
}
}
public void waitToCommit() throws SharedObjectAddAbortException {
synchronized (lock) {
long end = System.currentTimeMillis() + timeout;
try {
while (!votingCompleted()) {
long wait = end - System.currentTimeMillis();
if (wait <= 0L)
throw new SharedObjectAddAbortException(
Messages.TransactionSharedObject_EXCEPTION_TIMEOUT);
// Actually wait right here
lock.wait(wait);
}
} catch (InterruptedException e) {
throw new SharedObjectAddAbortException(Messages.TransactionSharedObject_EXCEPTION_INTERUPTED);
} catch (SharedObjectAddAbortException e1) {
// Aborted for some reason. Clean up.
doAbort(e1);
}
// Success. Send commit to remotes and clean up before returning.
doCommit();
}
}
public byte getTransactionState() {
synchronized (lock) {
return state;
}
}
protected void doAbort(SharedObjectAddAbortException e)
throws SharedObjectAddAbortException {
// Send destroy message here so all remotes get destroyed, and we remove
// ourselves from local space as well.
destroySelf();
// Set our own state variable to ABORTED
state = ISharedObjectContainerTransaction.ABORTED;
// throw so caller gets exception and can deal with it
throw e;
}
public void doCommit() throws SharedObjectAddAbortException {
// Get current membership
int others = 0;
others = getContext().getGroupMemberIDs().length;
// Only forward commit message if the participantIDs array is not yet
// null,
// and the current membership is > 0 (we're connected to something)
if (participantIDs != null && others > 0) {
// Send replicaCommit message to all remote clients
try {
forwardMsgTo(null, SharedObjectMsg.createMsg((String) null,
REPLICA_COMMIT_MSG));
} catch (Exception e2) {
doAbort(new SharedObjectAddAbortException(
Messages.TransactionSharedObject_EXCEPTION_ON_COMMIT_MESSAGE, e2));
}
}
// Set state variable to committed.
state = ISharedObjectContainerTransaction.COMMITTED;
// Call local committed message
committed();
participantIDs = null;
failedParticipants = null;
}
protected void execMsgInvoke(SharedObjectMsg msg, ID fromID, Object o)
throws Exception {
if (o == this) {
// Object[] args = msg.getArgs();
String name = msg.getMethodName();
if (name.equals(REPLICA_COMMIT_MSG)) {
replicaCommit();
return;
}
}
super.execMsgInvoke(msg, fromID, o);
}
public final void replicaCommit() {
synchronized (lock) {
state = COMMITTED;
lock.notifyAll();
participantIDs = null;
failedParticipants = null;
}
// Call subclass overrideable method
committed();
}
protected void committed() {
// Subclasses may override as appropriate
}
protected boolean votingCompleted() throws SharedObjectAddAbortException {
// The test here is is we've received any indication of failed
// participants in
// the transaction. If so, we throw.
if (failedParticipants != null && failedParticipants.size() > 0) {
ID remoteID = (ID) failedParticipants.keys().nextElement();
Exception e = (Exception) failedParticipants.get(remoteID);
// Abort!
throw new SharedObjectAddAbortException(Messages.TransactionSharedObject_EXCEPTION_FROM_ABORT, e);
// If no problems, and the number of participants to here from is 0,
// then we're done
} else if (state == ISharedObjectContainerTransaction.VOTING
&& participantIDs.size() == 0) {
// Success!
return true;
}
// Else continue waiting
return false;
}
}