/****************************************************************************
* 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.core.sharedobject;
import java.io.IOException;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.identity.IIdentifiable;
import org.eclipse.ecf.core.sharedobject.events.*;
import org.eclipse.ecf.core.sharedobject.util.IQueueEnqueue;
import org.eclipse.ecf.core.sharedobject.util.QueueException;
import org.eclipse.ecf.core.util.*;
import org.eclipse.ecf.internal.core.sharedobject.Activator;
import org.eclipse.ecf.internal.core.sharedobject.SharedObjectDebugOptions;
/**
* Base class for shared object classes. This base class provides a number of
* utility method for subclasses to use for tracing (e.g.
* {@link #traceCatching(String, Throwable)}, {@link #traceEntering(String)},
* {@link #traceExiting(String)}) logging (e.g.
* {@link #log(int, String, Throwable)}), as well as methods to access the
* {@link ISharedObjectContext} for the shared object instance (e.g.
* {@link #getID()}, {@link #getHomeContainerID()}, {@link #getContext()},
* {@link #getConfig()}, {@link #getProperties()}, {@link #isConnected()},
* {@link #isPrimary()}, etc). Also provided are a number of methods for
* sending messages to remote replica shared objects (e.g.
* {@link #sendSharedObjectMsgTo(ID, SharedObjectMsg)},
* {@link #sendSharedObjectMsgToPrimary(SharedObjectMsg)},
* {@link #sendSharedObjectMsgToSelf(SharedObjectMsg)}) and methods for
* replicating oneself to remote containers (e.g.
* {@link #replicateToRemoteContainers(ID[])}). Finally, object lifecycle
* methods are also provided (e.g. {@link #initialize()},
* {@link #creationCompleted()}, {@link #dispose(ID)}).
*
* Subclasses may use and override these methods as appropriate.
*
*/
public class BaseSharedObject implements ISharedObject, IIdentifiable {
protected static final int DESTROYREMOTE_CODE = 8001;
protected static final int DESTROYSELFLOCAL_CODE = 8002;
private ISharedObjectConfig config = null;
private List eventProcessors = new Vector();
public BaseSharedObject() {
super();
}
/* (non-Javadoc)
* @see org.eclipse.ecf.core.sharedobject.ISharedObject#init(org.eclipse.ecf.core.sharedobject.ISharedObjectConfig)
*/
public final void init(ISharedObjectConfig initData) throws SharedObjectInitException {
this.config = initData;
traceEntering("init", initData); //$NON-NLS-1$
addEventProcessor(new SharedObjectMsgEventProcessor(this));
initialize();
traceExiting("init"); //$NON-NLS-1$
}
/**
* Initialize this shared object. Subclasses may override as appropriate to
* define custom initialization behavior. If initialization should fail,
* then a SharedObjectInitException should be thrown by implementing code.
* Also, subclasses overriding this method should call super.initialize()
* before running their own code.
*
* @throws SharedObjectInitException
* if initialization should throw
*/
protected void initialize() throws SharedObjectInitException {
traceEntering("initialize"); //$NON-NLS-1$
}
/**
* Called by replication strategy code (e.g. two phase commit) when creation
* is completed (i.e. when transactional replication completed
* successfully). Subclasses that need to be notified when creation is
* completed should override this method.
*
*/
protected void creationCompleted() {
traceEntering("creationCompleted", null); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.ecf.core.sharedobject.ISharedObject#dispose(org.eclipse.ecf.core.identity.ID)
*/
public void dispose(ID containerID) {
traceEntering("dispose", containerID); //$NON-NLS-1$
eventProcessors.clear();
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter.isInstance(this)) {
return this;
}
Activator activator = Activator.getDefault();
if (activator == null)
return null;
final IAdapterManager adapterManager = activator.getAdapterManager();
if (adapterManager == null)
return null;
return adapterManager.loadAdapter(this, adapter.getName());
}
/* (non-Javadoc)
* @see org.eclipse.ecf.core.sharedobject.ISharedObject#handleEvent(org.eclipse.ecf.core.util.Event)
*/
public void handleEvent(Event event) {
traceEntering("handleEvent", event); //$NON-NLS-1$
fireEventProcessors(event);
traceExiting("handleEvent"); //$NON-NLS-1$
}
/**
* Add an event processor to the set of event processors available.
* @param proc the event processor to add. Must not be <code>null</code>.
* @return <code>true</code> if actually added, <code>false</code> otherwise.
*/
@SuppressWarnings("unchecked")
public boolean addEventProcessor(IEventProcessor proc) {
Assert.isNotNull(proc);
synchronized (eventProcessors) {
return eventProcessors.add(proc);
}
}
/**
* Remove an event processor from the set of event processors available to this object.
* @param proc the event processor to remove. Must not be <code>null</code>.
* @return <code>true</code> if actually removed, <code>false</code> otherwise.
*/
public boolean removeEventProcessor(IEventProcessor proc) {
Assert.isNotNull(proc);
synchronized (eventProcessors) {
return eventProcessors.remove(proc);
}
}
/**
* Clear event processors.
*/
public void clearEventProcessors() {
synchronized (eventProcessors) {
eventProcessors.clear();
}
}
/**
* Method called when an event is not handled by any event processor.
* @param event the event that was not handled.
*/
protected void handleUnhandledEvent(Event event) {
// By default, simply log as warning
Activator.getDefault().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, IStatus.WARNING, "handleUnhandledEvent=" + event, null)); //$NON-NLS-1$
}
/**
* Fire the current set of event processors with given event.
* @param event the event to deliver to event processors.
*/
@SuppressWarnings("unchecked")
protected void fireEventProcessors(Event event) {
if (event == null)
return;
Event evt = event;
List notify = null;
synchronized (eventProcessors) {
notify = new ArrayList(eventProcessors);
}
if (notify.size() == 0) {
handleUnhandledEvent(evt);
return;
}
for (Iterator i = notify.iterator(); i.hasNext();) {
IEventProcessor ep = (IEventProcessor) i.next();
if (ep.processEvent(evt))
break;
}
}
/* (non-Javadoc)
* @see org.eclipse.ecf.core.sharedobject.ISharedObject#handleEvents(org.eclipse.ecf.core.util.Event[])
*/
public void handleEvents(Event[] events) {
traceEntering("handleEvents", events); //$NON-NLS-1$
if (events == null)
return;
for (int i = 0; i < events.length; i++) {
handleEvent(events[i]);
}
traceExiting("handleEvents"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.ecf.core.identity.IIdentifiable#getID()
*/
public ID getID() {
return getConfig().getSharedObjectID();
}
/**
* Get the config for this shared object.
*
* @return ISharedObjectConfig for this object. The ISharedObjectConfig is
* set within {@link #init(ISharedObjectConfig)}. Will not be <code>null</code> as long as the init method
* is called prior to this method being called.
*/
protected final ISharedObjectConfig getConfig() {
return config;
}
/**
* Get the shared object context for this object.
*
* @return ISharedObjectContext the context. Will not be <code>null</code>.
*/
protected final ISharedObjectContext getContext() {
return getConfig().getContext();
}
/**
* @return ID that is the home container ID (primary) for this shared object. Will not be <code>null</code>
* as long as the {@link #init(ISharedObjectConfig)} method has been called (by container) as a result
* of {@link ISharedObjectManager#addSharedObject(ID, ISharedObject, Map)}.
*/
protected ID getHomeContainerID() {
return getConfig().getHomeContainerID();
}
/**
* @return ID that is the local container ID for this shared object. Will be <code>null</code> if
* the shared object is *not* in a local container (i.e. has been removed from the container).
*/
protected ID getLocalContainerID() {
ISharedObjectContext context = getContext();
return (context == null) ? null : context.getLocalContainerID();
}
/**
* @return ID the connected ID for the container that contains this shared object. Will be non-<code>null</code>
* if the surrounding container is not currently connected.
*/
protected ID getConnectedID() {
ISharedObjectContext context = getContext();
return (context == null) ? null : context.getConnectedID();
}
/**
* @return <code>true</code> if the surrounding container is currently connected, <code>false</code> otherwise.
*/
protected final boolean isConnected() {
return (getConnectedID() != null);
}
/**
* @return <code>true</code> if this shared object replica is the <b>primary</b>. The definition of primary
* is whether the {@link #getLocalContainerID()} and {@link #getHomeContainerID()} values are equal.
*/
protected final boolean isPrimary() {
ID local = getLocalContainerID();
ID home = getHomeContainerID();
if (local == null || home == null) {
return false;
}
return (local.equals(home));
}
/**
* @return Map any properties associated with this shared object via the ISharedObjectConfig provided
* upon {@link #init(ISharedObjectConfig)}.
*/
protected final Map<String, ?> getProperties() {
return getConfig().getProperties();
}
/**
* Destroy this shared object in the context of the current container. Destroys both local copy and
* any replicas present in remote containers.
*/
protected void destroySelf() {
traceEntering("destroySelf"); //$NON-NLS-1$
if (isPrimary()) {
try {
// Send destroy message to all known remotes
destroyRemote(null);
} catch (IOException e) {
traceCatching("destroySelf", e); //$NON-NLS-1$
log(DESTROYREMOTE_CODE, "destroySelf", e); //$NON-NLS-1$
}
}
// Now destroy self locally
destroySelfLocal();
traceExiting("destroySelf"); //$NON-NLS-1$
}
/**
* Destroy the local copy of this shared object in the current container.
*/
protected void destroySelfLocal() {
traceEntering("destroySelfLocal"); //$NON-NLS-1$
try {
ISharedObjectManager manager = getContext().getSharedObjectManager();
if (manager != null) {
manager.removeSharedObject(getID());
}
} catch (Exception e) {
traceCatching("destroySelfLocal", e); //$NON-NLS-1$
log(DESTROYSELFLOCAL_CODE, "destroySelfLocal", e); //$NON-NLS-1$
}
traceExiting("destroySelfLocal"); //$NON-NLS-1$
}
/**
* @param remoteID the ID of the remote container where the replica should be destroyed.
* @throws IOException if the destroy message cannot be sent (i.e. due to disconnection, etc).
*/
protected void destroyRemote(ID remoteID) throws IOException {
ISharedObjectContext context = getContext();
if (context != null)
context.sendDispose(remoteID);
}
/**
* Send SharedObjectMessage to container with given ID. The toID parameter
* may be null, and if null the message will be delivered to <b>all</b>
* containers in group. The second parameter may not be null.
*
* @param toID
* the target container ID for the SharedObjectMsg. If null, the
* given message is sent to all other containers currently in
* group
* @param msg
* the message instance to send
* @throws IOException
* thrown if the local container is not connected or unable to
* send for other reason
*/
protected void sendSharedObjectMsgTo(ID toID, SharedObjectMsg msg) throws IOException {
ISharedObjectContext context = getContext();
String method = "sendSharedObjectMsgTo"; //$NON-NLS-1$
traceEntering(method, new Object[] {toID, msg});
if (context != null) {
Assert.isNotNull(msg, "SharedObjectMsg cannot be null"); //$NON-NLS-1$
context.sendMessage(toID, new SharedObjectMsgEvent(getID(), toID, msg));
} else {
trace(method, "No shared object context available, so no message sent"); //$NON-NLS-1$
}
traceExiting(method);
}
/**
* Send SharedObjectMsg to this shared object's primary instance.
*
* @param msg
* the message instance to send
* @throws IOException
* throws if the local container is not connect or unable to
* send for other reason
*/
protected void sendSharedObjectMsgToPrimary(SharedObjectMsg msg) throws IOException {
sendSharedObjectMsgTo(getHomeContainerID(), msg);
}
/**
* Send SharedObjectMsg to local shared object. This places the given
* message at the end of this shared object's message queue for processing.
*
* @param msg
* the message instance to send.
*/
protected void sendSharedObjectMsgToSelf(SharedObjectMsg msg) {
if (msg == null)
throw new NullPointerException("SharedObjectMsg cannot be null"); //$NON-NLS-1$
ISharedObjectContext context = getContext();
if (context == null)
return;
IQueueEnqueue queue = context.getQueue();
try {
queue.enqueue(new SharedObjectMsgEvent(getID(), getContext().getLocalContainerID(), msg));
} catch (QueueException e) {
traceCatching("sendSharedObjectMsgToSelf", e); //$NON-NLS-1$
log(DESTROYREMOTE_CODE, "sendSharedObjectMsgToSelf", e); //$NON-NLS-1$
}
}
/**
* Get SharedObjectMsg from ISharedObjectMessageEvent.
* ISharedObjectMessageEvents can come from both local and remote sources.
* In the remote case, the SharedObjectMsg has to be retrieved from the
* RemoteSharedObjectEvent rather than the
* ISharedObjectMessageEvent.getData() directly. This method will provide a
* non-null SharedObjectMsg if it's provided either via remotely or locally.
* Returns null if the given event does not provide a valid SharedObjectMsg.
*
* @param event
* @return SharedObjectMsg the SharedObjectMsg delivered by the given event
*/
protected SharedObjectMsg getSharedObjectMsgFromEvent(ISharedObjectMessageEvent event) {
traceEntering("getSharedObjectMsgFromEvent", event); //$NON-NLS-1$
Object eventData = event.getData();
Object msgData = null;
// If eventData is not null and instanceof RemoteSharedObjectEvent
// then its a remote event and we extract the SharedObjectMsgEvent it
// contains and get it's data
if (eventData != null && eventData instanceof RemoteSharedObjectEvent) {
// It's a remote event
Object rsoeData = ((RemoteSharedObjectEvent) event).getData();
if (rsoeData != null && rsoeData instanceof SharedObjectMsgEvent)
msgData = ((SharedObjectMsgEvent) rsoeData).getData();
} else
msgData = eventData;
if (msgData != null && msgData instanceof SharedObjectMsg) {
traceExiting("getSharedObjectMsgFromEvent", msgData); //$NON-NLS-1$
return (SharedObjectMsg) msgData;
}
traceExiting("getSharedObjectMsgFromEvent", null); //$NON-NLS-1$
return null;
}
/**
* Handle a ISharedObjectMessageEvent. This method will be automatically
* called by the SharedObjectMsgEventProcessor when a
* ISharedObjectMessageEvent is received. The SharedObjectMsgEventProcessor
* is associated with this object via the initialize() method
*
* @param event
* the event to handle
* @return true if the provided event should receive no further processing.
* If false the provided Event should be passed to subsequent event
* processors.
*/
protected boolean handleSharedObjectMsgEvent(ISharedObjectMessageEvent event) {
traceEntering("handleSharedObjectMsgEvent", event); //$NON-NLS-1$
boolean result = false;
if (event instanceof ISharedObjectCreateResponseEvent)
result = handleSharedObjectCreateResponseEvent((ISharedObjectCreateResponseEvent) event);
else {
SharedObjectMsg msg = getSharedObjectMsgFromEvent(event);
if (msg != null)
result = handleSharedObjectMsg(event.getRemoteContainerID(), msg);
}
traceExiting("handleSharedObjectMsgEvent", result ? Boolean.TRUE : Boolean.FALSE); //$NON-NLS-1$
return result;
}
/**
* @since 2.4
*/
protected boolean handleSharedObjectMsg(ID fromID, SharedObjectMsg msg) {
return handleSharedObjectMsg(msg);
}
/**
* Handle a ISharedObjectCreateResponseEvent. This handler is called by
* handleSharedObjectMsgEvent when the ISharedObjectMessageEvent is of type
* ISharedObjectCreateResponseEvent. This default implementation simply
* returns false. Subclasses may override as appropriate. Note that if
* return value is true, it will prevent subsequent event processors from
* having a chance to process event
*
* @param createResponseEvent
* the ISharedObjectCreateResponseEvent received
* @return true if the provided event should receive no further processing.
* If false the provided Event should be passed to subsequent event
* processors.
*/
protected boolean handleSharedObjectCreateResponseEvent(ISharedObjectCreateResponseEvent createResponseEvent) {
return false;
}
/**
* SharedObjectMsg handler method. This method will be called by
* {@link #handleSharedObjectMsgEvent(ISharedObjectMessageEvent)} when a
* SharedObjectMsg is received either from a local source or a remote
* source. This default implementation simply returns false so that other
* processing of of the given msg can occur. Subclasses should override this
* behavior to define custom logic for handling SharedObjectMsgs.
*
* @param msg
* the SharedObjectMsg received
* @return true if the msg has been completely handled and subsequent
* processing should stop. False if processing should continue
*/
protected boolean handleSharedObjectMsg(SharedObjectMsg msg) {
return false;
}
/**
* Get a ReplicaSharedObjectDescription for a replica to be created on a
* given receiver.
*
* @param receiver
* the receiver the ReplicaSharedObjectDescription is for
* @return ReplicaSharedObjectDescription to be associated with given
* receiver. A non-null ReplicaSharedObjectDescription <b>must</b>
* be returned.
*/
protected ReplicaSharedObjectDescription getReplicaDescription(ID receiver) {
traceEntering("getReplicaDescription", receiver); //$NON-NLS-1$
ReplicaSharedObjectDescription result = new ReplicaSharedObjectDescription(getClass(), getID(), getConfig().getHomeContainerID(), getConfig().getProperties());
traceExiting("getReplicaDescription", result); //$NON-NLS-1$
return result;
}
/**
* This method is called by replicateToRemoteContainers to determine the
* ReplicaSharedObjectDescriptions associated with the given receivers.
* Receivers may be null (meaning that all in group are to be receivers),
* and if so then this method should return a ReplicaSharedObjectDescription []
* of length 1 with a single ReplicaSharedObjectDescription that will be
* used for all receivers. If receivers is non-null, then the
* ReplicaSharedObjectDescription [] result must be of <b>same length</b>
* as the receivers array. This method calls the getReplicaDescription
* method to create a replica description for each receiver. If this method
* returns null, <b>null replication is done</b>.
*
* @param receivers
* an ID[] of the intended receivers for the resulting
* ReplicaSharedObjectDescriptions. If null, then the <b>entire
* current group</b> is assumed to be the target, and this
* method should return a ReplicaSharedObjectDescriptions array
* of length 1, with a single ReplicaSharedObjectDescriptions for
* all target receivers.
*
* @return ReplicaSharedObjectDescription[] to determine replica
* descriptions for each receiver. A null return value indicates
* that no replicas are to be created. If the returned array is not
* null, then it <b>must</b> be of same length as the receivers
* parameter.
*
*/
protected ReplicaSharedObjectDescription[] getReplicaDescriptions(ID[] receivers) {
traceEntering("getReplicaDescriptions", receivers); //$NON-NLS-1$
ReplicaSharedObjectDescription[] descriptions = null;
if (receivers == null || receivers.length == 1) {
descriptions = new ReplicaSharedObjectDescription[1];
descriptions[0] = getReplicaDescription((receivers == null) ? null : receivers[0]);
} else {
descriptions = new ReplicaSharedObjectDescription[receivers.length];
for (int i = 0; i < receivers.length; i++) {
descriptions[i] = getReplicaDescription(receivers[i]);
}
}
traceExiting("getReplicaDescriptions", descriptions); //$NON-NLS-1$
return descriptions;
}
/**
* Get IDs of remote containers currently in this group. This method
* consults the current container context to retrieve the current group
* membership
*
* @return ID[] of current group membership. Will not return null;
*
* @see ISharedObjectContext#getGroupMemberIDs()
*/
protected ID[] getGroupMemberIDs() {
ISharedObjectContext context = getContext();
return (context == null) ? new ID[] {} : context.getGroupMemberIDs();
}
/**
* Replicate this shared object to a given set of remote containers. This
* method will invoke the method getReplicaDescriptions in order to
* determine the set of ReplicaSharedObjectDescriptions to send to remote
* containers.
*
* @param remoteContainers
* the set of remote containers to replicate to. If null, <b>all</b>
* containers in the current group are sent a message to create a
* replica of this shared object.
*/
protected void replicateToRemoteContainers(ID[] remoteContainers) {
ISharedObjectContext context = getContext();
if (context != null) {
traceEntering("replicateToRemoteContainers", remoteContainers); //$NON-NLS-1$
try {
// Get current group membership
ReplicaSharedObjectDescription[] createInfos = getReplicaDescriptions(remoteContainers);
if (createInfos != null) {
if (createInfos.length == 1) {
context.sendCreate((remoteContainers == null) ? null : remoteContainers[0], createInfos[0]);
} else {
for (int i = 0; i < remoteContainers.length; i++) {
context.sendCreate(remoteContainers[i], createInfos[i]);
}
}
}
} catch (IOException e) {
traceCatching("replicateToRemoteContainers." + DESTROYREMOTE_CODE, //$NON-NLS-1$
e);
log(DESTROYREMOTE_CODE, "replicateToRemoteContainers", e); //$NON-NLS-1$
}
}
}
protected void log(int code, String method, Throwable e) {
Activator.getDefault().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, code, getSharedObjectAsString(method), e));
}
protected void log(String method, Throwable e) {
log(IStatus.ERROR, method, e);
}
private String getSharedObjectAsString(String suffix) {
StringBuffer buf = new StringBuffer(String.valueOf(getID()));
buf.append(((isPrimary()) ? ".p." : ".r.")); //$NON-NLS-1$ //$NON-NLS-2$
buf.append(suffix);
return buf.toString();
}
protected void traceEntering(String methodName) {
Trace.entering(Activator.PLUGIN_ID, SharedObjectDebugOptions.METHODS_ENTERING, this.getClass(), getSharedObjectAsString(methodName));
}
protected void traceEntering(String methodName, Object[] params) {
Trace.entering(Activator.PLUGIN_ID, SharedObjectDebugOptions.METHODS_ENTERING, this.getClass(), getSharedObjectAsString(methodName));
}
protected void traceEntering(String methodName, Object param) {
Trace.entering(Activator.PLUGIN_ID, SharedObjectDebugOptions.METHODS_ENTERING, this.getClass(), getSharedObjectAsString(methodName));
}
protected void traceExiting(String methodName) {
Trace.entering(Activator.PLUGIN_ID, SharedObjectDebugOptions.METHODS_EXITING, this.getClass(), getSharedObjectAsString(methodName));
}
protected void traceExiting(String methodName, Object result) {
Trace.entering(Activator.PLUGIN_ID, SharedObjectDebugOptions.METHODS_EXITING, this.getClass(), getSharedObjectAsString(methodName));
}
protected void traceCatching(String method, Throwable t) {
Trace.catching(Activator.PLUGIN_ID, SharedObjectDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), getSharedObjectAsString(method), t);
}
/**
* @since 2.2
*/
protected void trace(String method, String message) {
Trace.trace(Activator.PLUGIN_ID, SharedObjectDebugOptions.DEBUG, this.getClass(), method, getSharedObjectAsString(method) + ": " + message); //$NON-NLS-1$
}
}