36624d96ac
they arrive at the client. Mmm... network efficiency++. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2396 542714f4-19e9-0310-aa3c-eee0fc999fb1
865 lines
29 KiB
Java
865 lines
29 KiB
Java
//
|
|
// $Id: DObject.java,v 1.62 2003/04/10 17:48:42 mdb Exp $
|
|
|
|
package com.threerings.presents.dobj;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.util.ArrayList;
|
|
|
|
import com.samskivert.util.ListUtil;
|
|
import com.samskivert.util.StringUtil;
|
|
|
|
import com.threerings.io.Streamable;
|
|
|
|
import com.threerings.presents.Log;
|
|
|
|
/**
|
|
* The distributed object forms the foundation of the Presents system. All
|
|
* information shared among users of the system is done via distributed
|
|
* objects. A distributed object has a set of listeners. These listeners
|
|
* have access to the object or a proxy of the object and therefore have
|
|
* access to the data stored in the object's members at all times.
|
|
*
|
|
* <p> Additionally, an object as a set of subscribers. Subscribers manage
|
|
* the lifespan of the object; while a subscriber is subscribed, the
|
|
* listeners registered with an object will be notified of events. When
|
|
* the subscriber unsubscribes, the object becomes non-live and the
|
|
* listeners are no longer notified. <em>Note:</em> on the server, object
|
|
* subscription is merely a formality as all objects remain live all the
|
|
* time, so <em>do not</em> depend on event notifications ceasing when a
|
|
* subscriber has relinquished its subscription. Always unregister all
|
|
* listeners when they no longer need to hear from an object.
|
|
*
|
|
* <p> When there is any change to the the object's fields data, an event
|
|
* is generated which is dispatched to all listeners of the object,
|
|
* notifying them of that change and effecting that change to the copy of
|
|
* the object maintained at each client. In this way, both a respository
|
|
* of shared information and a mechanism for asynchronous notification are
|
|
* made available as a fundamental application building blocks.
|
|
*
|
|
* <p> To define what information is shared, an application creates a
|
|
* distributed object declaration which is much like a class declaration
|
|
* except that it is transformed into a proper derived class of
|
|
* <code>DObject</code> by a script. A declaration looks something like
|
|
* this:
|
|
*
|
|
* <pre>
|
|
* public dclass RoomObject
|
|
* {
|
|
* public String description;
|
|
* public int[] occupants;
|
|
* }
|
|
* </pre>
|
|
*
|
|
* which is converted into an actual Java class that looks like this:
|
|
*
|
|
* <pre>
|
|
* public class RoomObject extends DObject
|
|
* {
|
|
* public String getDescription ()
|
|
* {
|
|
* // ...
|
|
* }
|
|
*
|
|
* public void setDescription (String description)
|
|
* {
|
|
* // ...
|
|
* }
|
|
*
|
|
* public int[] getOccupants ()
|
|
* {
|
|
* // ...
|
|
* }
|
|
*
|
|
* public void setOccupants (int[] occupants)
|
|
* {
|
|
* // ...
|
|
* }
|
|
*
|
|
* public void setOccupantsAt (int index, int value)
|
|
* {
|
|
* // ...
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* These method calls on the actual distributed object will result in the
|
|
* proper attribute change events being generated and dispatched.
|
|
*
|
|
* <p> Note that distributed object fields can be any of the following set
|
|
* of primitive types:
|
|
*
|
|
* <code><pre>
|
|
* boolean, byte, short, int, long, float, double
|
|
* Boolean, Byte, Short, Integer, Long, Float, Double, String
|
|
* boolean[], byte[], short[], int[], long[], float[], double[], String[]
|
|
* </pre></code>
|
|
*
|
|
* Fields of type {@link Streamable} can also be used.
|
|
*/
|
|
public class DObject implements Streamable
|
|
{
|
|
/**
|
|
* Returns the object id of this object. All objects in the system
|
|
* have a unique object id.
|
|
*/
|
|
public int getOid ()
|
|
{
|
|
return _oid;
|
|
}
|
|
|
|
/**
|
|
* Don't call this function! Go through the distributed object manager
|
|
* instead to ensure that everything is done on the proper thread.
|
|
* This function can only safely be called directly when you know you
|
|
* are operating on the omgr thread (you are in the middle of a call
|
|
* to <code>objectAvailable</code> or to a listener callback).
|
|
*
|
|
* @see DObjectManager#subscribeToObject
|
|
*/
|
|
public void addSubscriber (Subscriber sub)
|
|
{
|
|
// only add the subscriber if they're not already there
|
|
Object[] subs = ListUtil.testAndAdd(_subs, sub);
|
|
if (subs != null) {
|
|
// Log.info("Adding subscriber " + which() + ": " + sub + ".");
|
|
_subs = subs;
|
|
_scount++;
|
|
|
|
} else {
|
|
Log.warning("Refusing subscriber that's already in the list " +
|
|
"[dobj=" + which() + ", subscriber=" + sub + "]");
|
|
Thread.dumpStack();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Don't call this function! Go through the distributed object manager
|
|
* instead to ensure that everything is done on the proper thread.
|
|
* This function can only safely be called directly when you know you
|
|
* are operating on the omgr thread (you are in the middle of a call
|
|
* to <code>objectAvailable</code> or to a listener callback).
|
|
*
|
|
* @see DObjectManager#unsubscribeFromObject
|
|
*/
|
|
public void removeSubscriber (Subscriber sub)
|
|
{
|
|
if (ListUtil.clear(_subs, sub) != null) {
|
|
// if we removed something, check to see if we just removed
|
|
// the last subscriber from our list; we also want to be sure
|
|
// that we're still active otherwise there's no need to notify
|
|
// our objmgr because we don't have one
|
|
if (--_scount == 0 && _omgr != null) {
|
|
_omgr.removedLastSubscriber(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds an event listener to this object. The listener will be
|
|
* notified when any events are dispatched on this object that match
|
|
* their particular listener interface.
|
|
*
|
|
* <p> Note that the entity adding itself as a listener should have
|
|
* obtained the object reference by subscribing to it or should be
|
|
* acting on behalf of some other entity that subscribed to the
|
|
* object, <em>and</em> that it must be sure to remove itself from the
|
|
* listener list (via {@link #removeListener}) when it is done because
|
|
* unsubscribing from the object (done by whatever entity subscribed
|
|
* in the first place) is not guaranteed to result in the listeners
|
|
* added through that subscription being automatically removed (in
|
|
* most cases, they definitely will not be removed).
|
|
*
|
|
* @param listener the listener to be added.
|
|
*
|
|
* @see EventListener
|
|
* @see AttributeChangeListener
|
|
* @see SetListener
|
|
* @see OidListListener
|
|
*/
|
|
public void addListener (ChangeListener listener)
|
|
{
|
|
// only add the listener if they're not already there
|
|
Object[] els = ListUtil.testAndAdd(_listeners, listener);
|
|
if (els != null) {
|
|
_listeners = els;
|
|
} else {
|
|
Log.warning("Refusing repeat listener registration " +
|
|
"[dobj=" + which() + ", list=" + listener + "]");
|
|
Thread.dumpStack();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes an event listener from this object. The listener will no
|
|
* longer be notified when events are dispatched on this object.
|
|
*
|
|
* @param listener the listener to be removed.
|
|
*/
|
|
public void removeListener (ChangeListener listener)
|
|
{
|
|
ListUtil.clear(_listeners, listener);
|
|
}
|
|
|
|
/**
|
|
* Provides this object with an entity that can be used to validate
|
|
* subscription requests and events before they are processed. The
|
|
* access controller is handy for ensuring that clients are behaving
|
|
* as expected and for preventing impermissible modifications or event
|
|
* dispatches on a distributed object.
|
|
*/
|
|
public void setAccessController (AccessController controller)
|
|
{
|
|
_controller = controller;
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to the access controller in use by this object
|
|
* or null if none has been configured.
|
|
*/
|
|
public AccessController getAccessController ()
|
|
{
|
|
return _controller;
|
|
}
|
|
|
|
/**
|
|
* At times, an entity on the server may need to ensure that events it
|
|
* has queued up have made it through the event queue and are applied
|
|
* to their respective objects before a service may safely be
|
|
* undertaken again. To make this possible, it can acquire a lock on a
|
|
* distributed object, generate the events in question and then
|
|
* release the lock (via a call to <code>releaseLock</code>) which
|
|
* will queue up a final event, the processing of which will release
|
|
* the lock. Thus the lock will not be released until all of the
|
|
* previously generated events have been processed. If the service is
|
|
* invoked again before that lock is released, the associated call to
|
|
* <code>acquireLock</code> will fail and the code can respond
|
|
* accordingly. An object may have any number of outstanding locks as
|
|
* long as they each have a unique name.
|
|
*
|
|
* @param name the name of the lock to acquire.
|
|
*
|
|
* @return true if the lock was acquired, false if the lock was not
|
|
* acquired because it has not yet been released from a previous
|
|
* acquisition.
|
|
*
|
|
* @see #releaseLock
|
|
*/
|
|
public boolean acquireLock (String name)
|
|
{
|
|
// check for the existence of the lock in the list and add it if
|
|
// it's not already there
|
|
Object[] list = ListUtil.testAndAddEqual(_locks, name);
|
|
if (list == null) {
|
|
// a null list means the object was already in the list
|
|
return false;
|
|
|
|
} else {
|
|
// a non-null list means the object was added
|
|
_locks = list;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queues up an event that when processed will release the lock of the
|
|
* specified name.
|
|
*
|
|
* @see #acquireLock
|
|
*/
|
|
public void releaseLock (String name)
|
|
{
|
|
// queue up a release lock event
|
|
postEvent(new ReleaseLockEvent(_oid, name));
|
|
}
|
|
|
|
/**
|
|
* Don't call this function! It is called by a remove lock event when
|
|
* that event is processed and shouldn't be called at any other time.
|
|
* If you mean to release a lock that was acquired with
|
|
* <code>acquireLock</code> you should be using
|
|
* <code>releaseLock</code>.
|
|
*
|
|
* @see #acquireLock
|
|
* @see #releaseLock
|
|
*/
|
|
protected void clearLock (String name)
|
|
{
|
|
// clear the lock from the list
|
|
if (ListUtil.clearEqual(_locks, name) == null) {
|
|
// complain if we didn't find the lock
|
|
Log.info("Unable to clear non-existent lock [lock=" + name +
|
|
", dobj=" + this + "].");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests that this distributed object be destroyed. It does so by
|
|
* queueing up an object destroyed event which the server will
|
|
* validate and process.
|
|
*/
|
|
public void destroy ()
|
|
{
|
|
postEvent(new ObjectDestroyedEvent(_oid));
|
|
}
|
|
|
|
/**
|
|
* Checks to ensure that the specified subscriber has access to this
|
|
* object. This will be called before satisfying a subscription
|
|
* request. If an {@link AccessController} has been specified for this
|
|
* object, it will be used to determine whether or not to allow the
|
|
* subscription request. If no controller is set, the subscription
|
|
* will be allowed.
|
|
*
|
|
* @param sub the subscriber that will subscribe to this object.
|
|
*
|
|
* @return true if the subscriber has access to the object, false if
|
|
* they do not.
|
|
*/
|
|
public boolean checkPermissions (Subscriber sub)
|
|
{
|
|
if (_controller != null) {
|
|
return _controller.allowSubscribe(this, sub);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks to ensure that this event which is about to be processed,
|
|
* has the appropriate permissions. If an {@link AccessController} has
|
|
* been specified for this object, it will be used to determine
|
|
* whether or not to allow the even dispatch. If no controller is set,
|
|
* all events are allowed.
|
|
*
|
|
* @param event the event that will be dispatched, object permitting.
|
|
*
|
|
* @return true if the event is valid and should be dispatched, false
|
|
* if the event fails the permissions check and should be aborted.
|
|
*/
|
|
public boolean checkPermissions (DEvent event)
|
|
{
|
|
if (_controller != null) {
|
|
return _controller.allowDispatch(this, event);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the distributed object manager after it has applied an
|
|
* event to this object. This dispatches an event notification to all
|
|
* of the listeners registered with this object.
|
|
*
|
|
* @param event the event that was just applied.
|
|
*/
|
|
public void notifyListeners (DEvent event)
|
|
{
|
|
if (_listeners == null) {
|
|
return;
|
|
}
|
|
|
|
int llength = _listeners.length;
|
|
for (int i = 0; i < llength; i++) {
|
|
Object listener = _listeners[i];
|
|
if (listener == null) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
// do any event specific notifications
|
|
event.notifyListener(listener);
|
|
|
|
// and notify them if they are listening for all events
|
|
if (listener instanceof EventListener) {
|
|
((EventListener)listener).eventReceived(event);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.warning("Listener choked during notification " +
|
|
"[list=" + listener + ", event=" + event + "].");
|
|
Log.logStackTrace(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the distributed object manager after it has applied an
|
|
* event to this object. This dispatches an event notification to all
|
|
* of the proxy listeners registered with this object.
|
|
*
|
|
* @param event the event that was just applied.
|
|
*/
|
|
public void notifyProxies (DEvent event)
|
|
{
|
|
if (_subs == null) {
|
|
return;
|
|
}
|
|
|
|
for (int ii = 0, ll = _subs.length; ii < ll; ii++) {
|
|
Object sub = _subs[ii];
|
|
try {
|
|
if (sub != null && sub instanceof ProxySubscriber) {
|
|
((ProxySubscriber)sub).eventReceived(event);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.warning("Proxy choked during notification " +
|
|
"[sub=" + sub + ", event=" + event + "].");
|
|
Log.logStackTrace(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the named attribute to the specified value. This is only used
|
|
* by the internals of the event dispatch mechanism and should not be
|
|
* called directly by users. Use the generated attribute setter
|
|
* methods instead.
|
|
*/
|
|
public void setAttribute (String name, Object value)
|
|
throws ObjectAccessException
|
|
{
|
|
try {
|
|
// for values that contain other values (arrays and DSets), we
|
|
// need to clone them before putting them in the object
|
|
// because otherwise a subsequent event might come along and
|
|
// modify these values before the networking thread has had a
|
|
// chance to propagate this event to the clients
|
|
|
|
// i wish i could just call value.clone() but Object declares
|
|
// clone() to be inaccessible, so we must cast the values to
|
|
// their actual types to gain access to the widened clone()
|
|
// methods
|
|
if (value instanceof DSet) {
|
|
value = ((DSet)value).clone();
|
|
} else if (value instanceof int[]) {
|
|
value = ((int[])value).clone();
|
|
} else if (value instanceof String[]) {
|
|
value = ((String[])value).clone();
|
|
} else if (value instanceof byte[]) {
|
|
value = ((byte[])value).clone();
|
|
} else if (value instanceof long[]) {
|
|
value = ((long[])value).clone();
|
|
} else if (value instanceof float[]) {
|
|
value = ((float[])value).clone();
|
|
} else if (value instanceof short[]) {
|
|
value = ((short[])value).clone();
|
|
} else if (value instanceof double[]) {
|
|
value = ((double[])value).clone();
|
|
}
|
|
|
|
// now actually set the value
|
|
getClass().getField(name).set(this, value);
|
|
|
|
} catch (Exception e) {
|
|
String errmsg = "Attribute setting failure [name=" + name +
|
|
", value=" + value +
|
|
", vclass=" + value.getClass().getName() + "].";
|
|
throw new ObjectAccessException(errmsg, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Looks up the named attribute and returns a reference to it. This
|
|
* should only be used by the internals of the event dispatch
|
|
* mechanism and should not be called directly by users. Use the
|
|
* generated attribute getter methods instead.
|
|
*/
|
|
public Object getAttribute (String name)
|
|
throws ObjectAccessException
|
|
{
|
|
try {
|
|
return getClass().getField(name).get(this);
|
|
|
|
} catch (Exception e) {
|
|
String errmsg = "Attribute getting failure [name=" + name + "].";
|
|
throw new ObjectAccessException(errmsg, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Posts a message event on this distrubuted object.
|
|
*/
|
|
public void postMessage (String name, Object[] args)
|
|
{
|
|
postEvent(new MessageEvent(_oid, name, args));
|
|
}
|
|
|
|
/**
|
|
* Posts the specified event either to our dobject manager or to the
|
|
* compound event for which we are currently transacting.
|
|
*/
|
|
public void postEvent (DEvent event)
|
|
{
|
|
if (_tevent != null) {
|
|
_tevent.postEvent(event);
|
|
|
|
} else if (_omgr != null) {
|
|
_omgr.postEvent(event);
|
|
|
|
} else {
|
|
Log.warning("Unable to post event, object has no omgr " +
|
|
"[oid=" + getOid() + ", class=" + getClass().getName() +
|
|
", event=" + event + "].");
|
|
Thread.dumpStack();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if this object is active and registered with the
|
|
* distributed object system. If an object is created via
|
|
* <code>DObjectManager.createObject</code> it will be active until
|
|
* such time as it is destroyed.
|
|
*/
|
|
public boolean isActive ()
|
|
{
|
|
return _omgr != null;
|
|
}
|
|
|
|
/**
|
|
* Don't call this function! It initializes this distributed object
|
|
* with the supplied distributed object manager. This is called by the
|
|
* distributed object manager when an object is created and registered
|
|
* with the system.
|
|
*
|
|
* @see DObjectManager#createObject
|
|
*/
|
|
public void setManager (DObjectManager omgr)
|
|
{
|
|
_omgr = omgr;
|
|
}
|
|
|
|
/**
|
|
* Don't call this function. It is called by the distributed object
|
|
* manager when an object is created and registered with the system.
|
|
*
|
|
* @see DObjectManager#createObject
|
|
*/
|
|
public void setOid (int oid)
|
|
{
|
|
_oid = oid;
|
|
}
|
|
|
|
/**
|
|
* Generates a concise string representation of this object.
|
|
*/
|
|
public String which ()
|
|
{
|
|
StringBuffer buf = new StringBuffer();
|
|
which(buf);
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Used to briefly describe this distributed object.
|
|
*/
|
|
protected void which (StringBuffer buf)
|
|
{
|
|
buf.append(StringUtil.shortClassName(this));
|
|
buf.append(":").append(_oid);
|
|
}
|
|
|
|
/**
|
|
* Generates a string representation of this object.
|
|
*/
|
|
public String toString ()
|
|
{
|
|
StringBuffer buf = new StringBuffer();
|
|
toString(buf);
|
|
return buf.append("]").toString();
|
|
}
|
|
|
|
/**
|
|
* Generates a string representation of this object.
|
|
*/
|
|
protected void toString (StringBuffer buf)
|
|
{
|
|
StringUtil.fieldsToString(buf, this, "\n");
|
|
if (buf.length() > 0) {
|
|
buf.insert(0, "\n");
|
|
}
|
|
buf.insert(0, _oid);
|
|
buf.insert(0, "[oid=");
|
|
}
|
|
|
|
/**
|
|
* Begins a transaction on this distributed object. In some
|
|
* situations, it is desirable to cause multiple changes to
|
|
* distributed object fields in one unified operation. Starting a
|
|
* transaction causes all subsequent field modifications to be stored
|
|
* in a single compound event which can then be committed, dispatching
|
|
* and applying all included events in a single group. Additionally,
|
|
* the events are dispatched over the network in a single unit which
|
|
* can significantly enhance network efficiency.
|
|
*
|
|
* <p> When the transaction is complete, the caller must call {@link
|
|
* #commitTransaction} or {@link CompoundEvent#commit} to commit the
|
|
* transaction and release the object back to its normal
|
|
* non-transacting state. If the caller decides not to commit their
|
|
* transaction, they must call {@link #cancelTransaction} or {@link
|
|
* CompoundEvent#cancel} to cancel the transaction. Failure to do so
|
|
* will cause the pooch to be totally screwed.
|
|
*
|
|
* <p> Note: like all other distributed object operations,
|
|
* transactions are not thread safe. It is expected that a single
|
|
* thread will handle all distributed object operations and that
|
|
* thread will begin and complete a transaction before giving up
|
|
* control to unknown code which might try to operate on the
|
|
* transacting distributed object.
|
|
*
|
|
* <p> Note also: if the object is already engaged in a transaction, a
|
|
* transaction participant count will be incremented to note that an
|
|
* additional call to {@link #commitTransaction} is required before
|
|
* the transaction should actually be committed. Thus <em>every</em>
|
|
* call to {@link #startTransaction} must be accompanied by a call to
|
|
* either {@link #commitTransaction} or {@link
|
|
* #cancelTransaction}. Additionally, if any transaction participant
|
|
* cancels the transaction, the entire transaction is cancelled for
|
|
* all participants, regardless of whether the other participants
|
|
* attempted to commit the transaction.
|
|
*/
|
|
public void startTransaction ()
|
|
{
|
|
// sanity check
|
|
if (!isActive()) {
|
|
String errmsg = "Can't start transaction on non-active object " +
|
|
"[oid=" + getOid() + ", type=" + getClass().getName() + "]";
|
|
throw new IllegalStateException(errmsg);
|
|
}
|
|
|
|
if (_tevent != null) {
|
|
_tcount++;
|
|
} else {
|
|
_tevent = new CompoundEvent(this, _omgr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Commits the transaction in which this distributed object is
|
|
* involved.
|
|
*
|
|
* @see CompoundEvent#commit
|
|
*/
|
|
public void commitTransaction ()
|
|
{
|
|
if (_tevent == null) {
|
|
String errmsg = "Cannot commit: not involved in a transaction " +
|
|
"[dobj=" + this + "]";
|
|
throw new IllegalStateException(errmsg);
|
|
}
|
|
|
|
// if we are nested, we decrement our nesting count rather than
|
|
// committing the transaction
|
|
if (_tcount > 0) {
|
|
_tcount--;
|
|
|
|
} else {
|
|
// we may actually be doing our final commit after someone
|
|
// already cancelled this transaction, so we need to perform
|
|
// the appropriate action at this point
|
|
if (_tcancelled) {
|
|
_tevent.cancel();
|
|
} else {
|
|
_tevent.commit();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if this object is in the middle of a transaction or
|
|
* false if it is not.
|
|
*/
|
|
public boolean inTransaction ()
|
|
{
|
|
return (_tevent != null);
|
|
}
|
|
|
|
/**
|
|
* Cancels the transaction in which this distributed object is
|
|
* involved.
|
|
*
|
|
* @see CompoundEvent#cancel
|
|
*/
|
|
public void cancelTransaction ()
|
|
{
|
|
if (_tevent == null) {
|
|
String errmsg = "Cannot cancel: not involved in a transaction " +
|
|
"[dobj=" + this + "]";
|
|
throw new IllegalStateException(errmsg);
|
|
}
|
|
|
|
// if we're in a nested transaction, make a note that it is to be
|
|
// cancelled when all parties commit and decrement the nest count
|
|
if (_tcount > 0) {
|
|
_tcancelled = true;
|
|
_tcount--;
|
|
|
|
} else {
|
|
_tevent.cancel();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes this object from participation in any transaction in which
|
|
* it might be taking part.
|
|
*/
|
|
protected void clearTransaction ()
|
|
{
|
|
// sanity check
|
|
if (_tcount != 0) {
|
|
Log.warning("Transaction cleared with non-zero nesting count " +
|
|
"[dobj=" + this + "].");
|
|
_tcount = 0;
|
|
}
|
|
|
|
// clear our transaction state
|
|
_tevent = null;
|
|
_tcancelled = false;
|
|
}
|
|
|
|
/**
|
|
* Called by derived instances when an attribute setter method was
|
|
* called.
|
|
*/
|
|
protected void requestAttributeChange (String name, Object value)
|
|
{
|
|
// dispatch an attribute changed event
|
|
postEvent(new AttributeChangedEvent(_oid, name, value));
|
|
}
|
|
|
|
/**
|
|
* Called by derived instances when an element updater method was
|
|
* called.
|
|
*/
|
|
protected void requestElementUpdate (String name, Object value, int index)
|
|
{
|
|
// dispatch an attribute changed event
|
|
postEvent(new ElementUpdatedEvent(_oid, name, value, index));
|
|
}
|
|
|
|
/**
|
|
* Calls by derived instances when an oid adder method was called.
|
|
*/
|
|
protected void requestOidAdd (String name, int oid)
|
|
{
|
|
// dispatch an object added event
|
|
postEvent(new ObjectAddedEvent(_oid, name, oid));
|
|
}
|
|
|
|
/**
|
|
* Calls by derived instances when an oid remover method was called.
|
|
*/
|
|
protected void requestOidRemove (String name, int oid)
|
|
{
|
|
// dispatch an object removed event
|
|
postEvent(new ObjectRemovedEvent(_oid, name, oid));
|
|
}
|
|
|
|
/**
|
|
* Calls by derived instances when a set adder method was called.
|
|
*/
|
|
protected void requestEntryAdd (String name, DSet.Entry entry)
|
|
{
|
|
try {
|
|
DSet set = (DSet)getAttribute(name);
|
|
// if we're on the authoritative server, we update the set
|
|
// immediately
|
|
boolean alreadyApplied = false;
|
|
if (_omgr != null && _omgr.isManager(this)) {
|
|
// Log.info("Immediately adding [name=" + name +
|
|
// ", entry=" + entry + "].");
|
|
set.add(entry);
|
|
alreadyApplied = true;
|
|
}
|
|
// dispatch an entry added event
|
|
postEvent(new EntryAddedEvent(_oid, name, entry, alreadyApplied));
|
|
|
|
} catch (ObjectAccessException oae) {
|
|
Log.warning("Unable to request entryAdd [name=" + name +
|
|
", entry=" + entry + ", error=" + oae + "].");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls by derived instances when a set remover method was called.
|
|
*/
|
|
protected void requestEntryRemove (String name, Comparable key)
|
|
{
|
|
try {
|
|
DSet set = (DSet)getAttribute(name);
|
|
// if we're on the authoritative server, we update the set
|
|
// immediately
|
|
DSet.Entry oldEntry = null;
|
|
if (_omgr != null && _omgr.isManager(this)) {
|
|
// Log.info("Immediately removing [name=" + name +
|
|
// ", key=" + key + "].");
|
|
oldEntry = set.get(key);
|
|
set.removeKey(key);
|
|
}
|
|
// dispatch an entry removed event
|
|
postEvent(new EntryRemovedEvent(_oid, name, key, oldEntry));
|
|
|
|
} catch (ObjectAccessException oae) {
|
|
Log.warning("Unable to request entryRemove [name=" + name +
|
|
", key=" + key + ", error=" + oae + "].");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls by derived instances when a set updater method was called.
|
|
*/
|
|
protected void requestEntryUpdate (String name, DSet.Entry entry)
|
|
{
|
|
try {
|
|
DSet set = (DSet)getAttribute(name);
|
|
// if we're on the authoritative server, we update the set
|
|
// immediately
|
|
DSet.Entry oldEntry = null;
|
|
if (_omgr != null && _omgr.isManager(this)) {
|
|
// Log.info("Immediately updating [name=" + name +
|
|
// ", entry=" + entry + "].");
|
|
oldEntry = set.get(entry.getKey());
|
|
set.update(entry);
|
|
}
|
|
// dispatch an entry updated event
|
|
postEvent(new EntryUpdatedEvent(_oid, name, entry, oldEntry));
|
|
|
|
} catch (ObjectAccessException oae) {
|
|
Log.warning("Unable to request entryUpdate [name=" + name +
|
|
", entry=" + entry + ", error=" + oae + "].");
|
|
}
|
|
}
|
|
|
|
/** Our object id. */
|
|
protected int _oid;
|
|
|
|
/** A reference to our object manager. */
|
|
protected transient DObjectManager _omgr;
|
|
|
|
/** The entity that tells us if an event or subscription request
|
|
* should be allowed. */
|
|
protected transient AccessController _controller;
|
|
|
|
/** A list of outstanding locks. */
|
|
protected transient Object[] _locks;
|
|
|
|
/** Our subscribers list. */
|
|
protected transient Object[] _subs;
|
|
|
|
/** Our event listeners list. */
|
|
protected transient Object[] _listeners;
|
|
|
|
/** Our subscriber count. */
|
|
protected transient int _scount;
|
|
|
|
/** The compound event associated with our transaction, if we're
|
|
* currently in a transaction. */
|
|
protected transient CompoundEvent _tevent;
|
|
|
|
/** The nesting depth of our current transaction. */
|
|
protected transient int _tcount;
|
|
|
|
/** Whether or not our nested transaction has been cancelled. */
|
|
protected transient boolean _tcancelled;
|
|
}
|