/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.jini.outrigger; import java.io.IOException; import java.rmi.RemoteException; import java.rmi.MarshalledObject; import net.jini.core.event.RemoteEventListener; import net.jini.core.event.RemoteEvent; import net.jini.core.event.UnknownEventException; import net.jini.id.Uuid; import net.jini.security.ProxyPreparer; import net.jini.space.JavaSpace; import com.sun.jini.landlord.LeasedResource; /** * Subclass of <code>TransitionWatcher</code> for event * registrations. Also represents the registration itself. */ abstract class EventRegistrationWatcher extends TransitionWatcher implements EventRegistrationRecord { /** * The current expiration time of the registration * Protected, but only for use by subclasses. */ long expiration; /** * The UUID that identifies this registration * Protected, but only for use by subclasses. * Should not be changed. */ Uuid cookie; /** * The handback associated with this registration. * Protected, but only for use by subclasses. * Should not be changed. */ MarshalledObject handback; /** * The event ID associated with this registration * Protected, but only for use by subclasses. * Should not be changed. */ long eventID; /** * The current sequence number. */ private long currentSeqNum = 0; /** * The sequence number of the last event successfully * delivered. Protected, but only for use by subclasses. */ long lastSeqNumDelivered = -1; /** * The <code>TemplateHandle</code> associated with this * watcher. */ private TemplateHandle owner; /** * Used during log recovery to create a mostly empty * <code>EventRegistrationWatcher</code>. * <p> * Note, we set the time stamp and tie-breaker here instead of * getting them from the log. This means they will be inconstant * with their value from the last VM we were in, but since they * never leak out and events are read-only anyway this should not * be a problem (this also allows us to keep the tie-breaker and * time stamp in final fields). * * @param timestamp the value that is used * to sort <code>TransitionWatcher</code>s. * @param startOrdinal the highest ordinal associated * with operations that are considered to have occurred * before the operation associated with this watcher. * @param currentSeqNum Sequence number to start with. */ EventRegistrationWatcher(long timestamp, long startOrdinal, long currentSeqNum) { super(timestamp, startOrdinal); this.currentSeqNum = currentSeqNum; } /** * Create a new <code>EventRegistrationWatcher</code>. * @param timestamp the value that is used * to sort <code>TransitionWatcher</code>s. * @param startOrdinal the highest ordinal associated * with operations that are considered to have occurred * before the operation associated with this watcher. * @param cookie The unique identifier associated * with this watcher. Must not be <code>null</code>. * @param handback The handback object that * should be sent along with event * notifications to the the listener. * @param eventID The event ID for event type * represented by this object. * @throws NullPointerException if the <code>cookie</code> * argument is <code>null</code>. */ EventRegistrationWatcher(long timestamp, long startOrdinal, Uuid cookie, MarshalledObject handback, long eventID) { super(timestamp, startOrdinal); if (cookie == null) throw new NullPointerException("cookie must be non-null"); this.cookie = cookie; this.handback = handback; this.eventID = eventID; } /** * Process the given transition by queuing up a task with the * notifier for event delivery. Assumes the passed entry matches * the template in the <code>TemplateHandle</code> associated with * this watcher and that <code>isInterested</code> returned * <code>true</code>. If <code>remove</code> has been called or * the expiration time of this watcher has passed, this call * should have no effect. This call may cause the watcher to be * removed. * @param transition A <code>EntryTransition</code> that * describes the transition and what * entry is transitioning. This method * will assume that <code>transition.getHandle</code> * returns a non-null value. * @param now An estimate of the current time (not the time * when the event occured). * @throws NullPointerException if <code>transition</code> is * <code>null</code>. */ void process(EntryTransition transition, long now) { boolean doneFor = false; // lock before checking the time and so we can update currentSeqNum synchronized (this) { if (owner == null) return; // Must have been removed if (now > expiration) { doneFor = true; } else { currentSeqNum++; owner.getServer().enqueueDelivery(new BasicEventSender()); } } if (doneFor) cancel(); } /** * Return the remote listener associated with this * <code>EventRegistrationWatcher</code>. Optionally * prepare the listener if it has been recovered from * the store and not yet re-prepared. * @return the remote listener associated with this * <code>EventRegistrationWatcher</code>. * @throws IOException if the listener can not * be unmarshalled. May throw {@link RemoteException} * if the call to the preparer does * @throws ClassNotFoundException if the listener * needs to be unmarshalled and a necessary * class can not be found. * @throws SecurityException if the <code>prepareProxy</code> * call does. */ abstract RemoteEventListener getListener(ProxyPreparer preparer) throws ClassNotFoundException, IOException; /** * Associate a <code>TemplateHandle</code> with this object. May * only be called once on any given * <code>EventRegistrationWatcher</code> instance. * * @param h The <code>TemplateHandle</code> associated * with this watcher. * @return <code>true</code> if the handle was succfully added, * and <code>false</code> if the watcher has already * been removed. * @throws NullPointerException if <code>h</code> is * <code>null</code> */ boolean addTemplateHandle(TemplateHandle h) { if (h == null) throw new NullPointerException("TemplateHandle must be non-null"); if (owner != null) throw new AssertionError("Can only call addTemplateHandle once"); owner = h; return true; } /** * Set the expiration time of this object. This method may be * called before <code>setTemplateHandle</code> is called. * Assumes locking is handled by the caller. * @param newExpiration The expiration time. */ public void setExpiration(long newExpiration) { expiration = newExpiration; } public long getExpiration() { return expiration; } /** * Get the unique identifier associated with this object. This * method may be called before <code>setTemplateHandle</code> is * called. * @return The unique identifier associated with this * watcher. */ public Uuid getCookie() { return cookie; } /** * Overridden by subclasses if there is any cleanup work they need * to do as part of <code>cancel</code> or * <code>removeIfExpired</code>. Called after releasing the lock * on <code>this</code>. Will be called at most once. * @param owner A reference to the owner. * @param expired <code>true</code> if being called from * <code>removeIfExpired</code> and false otherwise. */ void cleanup(TemplateHandle owner, boolean expired) {} /** * The heavy lifting of removing ourselves. * @param now The current time (or a bit earlier). * @param doIt If <code>true</code> ignore * <code>now</code> and just remove <code>this</code> * object. * @return <code>true</code> if this call removed * <code>this</code> object, <code>false</code> if * it had already been done. Should be ignored if * <code>doIt</code> is <code>false</code>. */ private boolean doRemove(long now, boolean doIt) { final TemplateHandle owner; synchronized (this) { if (this.owner == null) return false; // already removed // Is this a force, or past our expiration? if (!doIt && (now < expiration)) return false; // don't remove, not our time owner = this.owner; expiration = Long.MIN_VALUE; //Make sure no one tries to renew us this.owner = null; //We might stick around don't hold owner } cleanup(owner, !doIt); owner.getServer().removeEventRegistration(this); owner.removeTransitionWatcher(this); return true; // we did the deed } void removeIfExpired(long now) { doRemove(now, false); } public boolean cancel() { return doRemove(0, true); } /** * Common implementation of <code>EventSender</code>. */ private class BasicEventSender implements EventSender { public void sendEvent(JavaSpace source, long now, ProxyPreparer preparer) throws UnknownEventException, IOException, ClassNotFoundException { boolean doneFor = false; long seqNum = -1; synchronized (EventRegistrationWatcher.this) { if (owner == null) return; // Our watcher must have been removed, we're done if (getExpiration() < now) { doneFor = true; // Our watcher is expired, remove it } else if (currentSeqNum <= lastSeqNumDelivered) { return; // Someone already sent our event, we're done } else { // We need to send an event! seqNum = currentSeqNum; } } // Now that we are outside the lock kill our watcher if doneFor. if (doneFor) { cancel(); return; } /* The only way to get here is through a path that sets * seqNum to the non-initial values */ assert seqNum != -1; /* If we are here then we need to send an event (probably * someone could have sent our event between the time * we released the lock and now). */ getListener(preparer).notify( new RemoteEvent(source, eventID, seqNum, handback)); // success!, update lastSeqNumDelivered, but don't go backward synchronized (EventRegistrationWatcher.this) { if (seqNum > lastSeqNumDelivered) lastSeqNumDelivered = seqNum; } } public void cancelRegistration() { cancel(); } /** * Return the <code>EventRegistrationWatcher</code> this * object is part of (exits because * <code>(BasicEventSender)other).EventRegistrationWatcher. * this</code> does not work. */ private EventRegistrationWatcher getOwner() { return EventRegistrationWatcher.this; } /** * Run after another event sender if it is for the same * registration. No sense sending the same event twice. Don't * care which one goes first (though the second probably won't * run since <code>lastSeqNumDelivered</code> will probably * equal <code>currentSeqNum</code> when it runs). */ public boolean runAfter(EventSender other) { if (!(other instanceof BasicEventSender)) return false; return EventRegistrationWatcher.this == ((BasicEventSender)other).getOwner(); } } }