/* * 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.norm; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.rmi.MarshalledObject; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import net.jini.core.event.EventRegistration; import net.jini.core.event.RemoteEvent; import net.jini.core.event.RemoteEventListener; import net.jini.core.lease.Lease; import net.jini.id.Uuid; import net.jini.io.MarshalledInstance; import net.jini.lease.ExpirationWarningEvent; import net.jini.lease.LeaseRenewalSet; import net.jini.security.ProxyPreparer; import com.sun.jini.landlord.LeasedResource; import com.sun.jini.norm.event.EventFactory; import com.sun.jini.norm.event.EventType; import com.sun.jini.norm.event.EventTypeGenerator; import com.sun.jini.norm.event.SendMonitor; /** * Norm's internal representation of LeaseRenewalSets. Unless otherwise * noted the methods of this class assume synchronization being handled * by the caller. * * @author Sun Microsystems, Inc. */ class LeaseSet implements Serializable, LeasedResource { private static final long serialVersionUID = 2; /** * Expiration time of the set * @serial */ private long expiration; /** * We keep two copies of the expiration time guarded by different * locks so the the client lease renewal threads don't have to wait * on the set's lock. */ private transient ExpirationTime expiration2; /** * ID that uniquely identifies this set * @serial */ private final Uuid ID; /** * A collection of the client leases in this set (in wrapped form). * @serial */ private final Set leases = new HashSet(); /** * A table that maps client leases to client lease wrappers. */ private transient LeaseTable leaseTable; /** * Time before <code>expiration</code> to send expiration warning events * @serial */ private long minWarning = NormServer.NO_LISTENER; /** * The event type for expiration warning events * @serial */ private EventType warningEventType; /** * The current sequence number for expiration warning events * @serial */ private long warningSeqNum; /** * The event type for failure events * @serial */ private EventType failureEventType; /** * <code>PersistentStore</code> that changes should be logged to. */ private transient PersistentStore store; /** * The <code>NormServerBaseImpl</code> are attached to */ private transient NormServerBaseImpl normServerBaseImpl; // Constructors and state restoration /** * Simple constructor. Note expiration will be set when we allocate * a lease for this set. * @param ID for this set * @param generator object set can use to create new event type objects * @param store <code>PersistentStore<code> that changes should be logged * to * @param normServerBaseImpl the <code>NormServerBaseImpl</code> that * created this set */ LeaseSet(Uuid ID, EventTypeGenerator generator, PersistentStore store, NormServerBaseImpl normServerBaseImpl) { this.store = store; this.normServerBaseImpl = normServerBaseImpl; this.ID = ID; // For completeness expiration2 = new ExpirationTime(expiration); leaseTable = new LeaseTable(); final SendMonitor sendMonitor = normServerBaseImpl.newSendMonitor(this); try { warningEventType = generator.newEventType( sendMonitor, LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID); failureEventType = generator.newEventType( sendMonitor, LeaseRenewalSet.RENEWAL_FAILURE_EVENT_ID); } catch (IOException e) { // Because we are passing null for the listener we will // never get an exception throw new AssertionError(); } } /** * Override readObject so we can restore expiration2 */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // Restore the 2nd copy expiration2 = new ExpirationTime(expiration); // Create lease table, but only populate after restoring lease wrapper // transient state leaseTable = new LeaseTable(); } /** * Restore the transient state of the set that can't be restored * automatically after a log recovery. * @param generator event type generator associated with this set's events * @param store <code>PersistentStore<code> that changes should be * logged to * @param normServerBaseImpl the <code>normServerBaseImpl</code> that * created this set * @param recoveredListenerPreparer the proxy preparer to use to prepare * recovered listeners * @return an iterator over the set of client leases */ Iterator restoreTransientState(EventTypeGenerator generator, PersistentStore store, NormServerBaseImpl normServerBaseImpl, ProxyPreparer recoveredListenerPreparer) { this.normServerBaseImpl = normServerBaseImpl; this.store = store; final SendMonitor sendMonitor = normServerBaseImpl.newSendMonitor(this); warningEventType.restoreTransientState( generator, sendMonitor, recoveredListenerPreparer); failureEventType.restoreTransientState( generator, sendMonitor, recoveredListenerPreparer); // Instead of logging the sequence number each time we send // a warning event (like we do for renewal failures), we just // record the current sequence number as of every snapshot and // assume we won't send more than Integer.MAX_VALUE warning events // between snapshots, nor will we crash and restart more than // ~ Long.MAX_VALUE/Integer.MAX_VALUE times. warningEventType.setLastSequenceNumber(warningSeqNum + Integer.MAX_VALUE); normServerBaseImpl.updateLeaseCount(leases.size()); return leases.iterator(); } /** * Return the wrapper for the specified client lease, or null if not * found. */ ClientLeaseWrapper getClientLeaseWrapper(Lease clientLease) { return leaseTable.get(clientLease); } /** * Utility method to replace a client lease in the lease set. Returns true * if an equal lease was not already present. Does not update the lease * table. */ private boolean replace(ClientLeaseWrapper clw) { boolean found = leases.remove(clw); leases.add(clw); return !found; } /** * Add or update the specified wrapped client lease to the set. * @param clw lease to be added or updated */ void update(ClientLeaseWrapper clw) { boolean added = replace(clw); leaseTable.put(clw); final Object u = new UpdateClientLease(this, clw); store.update(u); if (added) { normServerBaseImpl.updateLeaseCount(1); } } /** * Add a lease already in the lease set to the lease table. Used during * recovery to add the wrapper to the lease table only after it has been * given its recovery proxy preparer and attempting to unpack the lease has * attempted. */ void addToLeaseTable(ClientLeaseWrapper clw) { leaseTable.put(clw); } /** * Return true if the passed wrapper is in the set */ boolean doesContainWrapper(ClientLeaseWrapper clw) { return leases.contains(clw); } /** * Utility method to remove a client lease. Returns true if the lease is * removed. */ private boolean removeInternal(ClientLeaseWrapper clw) { if (leases.remove(clw)) { leaseTable.remove(clw); return true; } return false; } /** * Remove the specified wrapped client lease from the set. * @param clw lease to removed * @return false if the lease has already been removed, or * if logically the lease is no longer in the set (e.g. its * membership expiration has been reached) */ boolean remove(ClientLeaseWrapper clw) { if (!removeInternal(clw)) { // if we are here the lease must have already been // removed by some other thread, don't bother logging return false; } // Even if the membership expiration was expired we still // want to update the log final Object u = new RemoveClientLease(this, clw); store.update(u); normServerBaseImpl.updateLeaseCount(-1); return (clw.getMembershipExpiration() > System.currentTimeMillis()); } /** * Destroy a lease set * @return a <code>Set</code> with all of the sets WrappedClientLeases */ Set destroy() { setExpiration(-1); final Object u = new CancelLeaseSet(getUuid()); store.update(u); normServerBaseImpl.updateLeaseCount(-leases.size()); return leases; } /** * Return an array of leases in marshalled form. For each lease we have * in unmarshalled form serialize using the duration format, for each * lease we can't unmarshal just use the MarshalledInstance we have on * hand. If the set is empty return null. */ MarshalledInstance[] getLeases() { final long now = System.currentTimeMillis(); final Iterator i = leases.iterator(); final List l = new ArrayList(leases.size()); while (i.hasNext()) { final ClientLeaseWrapper clw = (ClientLeaseWrapper) i.next(); // Check to make sure the leases membership expiration has not // passed if (now > clw.getMembershipExpiration()) continue; l.add(clw.getMarshalledClientLease()); } if (l.isEmpty()) { return null; } else { return (MarshalledInstance[]) l.toArray( new MarshalledInstance[l.size()]); } } /** * Set/update/clear the expiration warning listener. * @param listener the new listener * @param minWarning how long before the lease on the set expires should * the event be sent * @param handback the new handback * @return if <code>listener</code> is non-<code>null</code> return * an <code>EventRegistration</code> otherwise return <code>null</code> * @throws IOException if listener cannot be serialized */ EventRegistration setExpirationWarningListener( RemoteEventListener listener, long minWarning, MarshalledObject handback) throws IOException { this.minWarning = minWarning; warningEventType.setListener(listener, handback); final Object u = new WarningEventRegistration(this); store.update(u); if (listener == null) return null; final SetProxy proxy = newSetProxy(); return new EventRegistration( warningEventType.getEventID(), proxy, proxy.getRenewalSetLease(), warningEventType.getLastSequenceNumber()); } /** * Set/update/clear the renewal failure listener * @param listener the new listener * @param handback the new handback * @return if <code>listener</code> is non-<code>null</code> return * an <code>EventRegistration</code> otherwise return <code>null</code> * @throws IOException if listener can not be serialized */ EventRegistration setRenewalFailureListener( RemoteEventListener listener, MarshalledObject handback) throws IOException { failureEventType.setListener(listener, handback); final Object u = new FailureEventRegistration(this); store.update(u); if (listener == null) return null; final SetProxy proxy = newSetProxy(); return new EventRegistration( failureEventType.getEventID(), proxy, proxy.getRenewalSetLease(), failureEventType.getLastSequenceNumber()); } /** * Handle failures to renew a lease by removing the lease from the set * and if needed schedule sending an event. * @param clw the wrapped client lease for the lease that could not * be removed */ void renewalFailure(ClientLeaseWrapper clw) { if (!removeInternal(clw)) { // If we are here the lease must have already been // removed by some other thread, don't bother logging or // sending an event. return; } Object u; EventFactory factory; long seqNum; try { factory = clw.newFailureFactory(newSetProxy()); seqNum = failureEventType.sendEvent(factory); } catch (IOException e) { // We need to log the event even if we can't send it. // Update the sequence number since an event has // occurred. Note, we only get here if creating the // factory fails (failureEventType.sendEvent() does not // throw IOException) so we there is no danger of // incrementing the sequence number twice // $$$ is this the right thing to do? seqNum = failureEventType.bumpSequenceNumber(); } u = new RenewalFailure(this, clw, seqNum); store.update(u); } /** * Send an expiration warning event for this set */ void sendWarningEvent() { warningSeqNum = warningEventType.sendEvent(new WarningFactory(this)); } /** * Nested class that implements <code>EventFactory</code> that * generates <code>ExpirationWarningEvent</code>s. */ private class WarningFactory implements EventFactory { private static final long serialVersionUID = 1L; /** The source for the event */ final private SetProxy proxy; /** * Create a new WarningFactory * @param set The set generating this event */ WarningFactory(LeaseSet set) { proxy = set.newSetProxy(); } // Inherit java doc from super type public RemoteEvent createEvent(long eventID, long seqNum, MarshalledObject handback) { return new ExpirationWarningEvent(proxy, seqNum, handback); } } /** * Create a new SetProxy for this set that has a lease with the * current expiration */ private SetProxy newSetProxy() { return normServerBaseImpl.newSetProxy(this); } /** * Return true if there is a non-<code>null</code> listener registered * for the expiration warning event. * <p> * Note, this method assumes the current thread owns the set's lock */ boolean haveWarningRegistration() { return warningEventType.haveListener(); } /** * Return the absolute time when a expiration warning should be sent. */ long getWarningTime() { return expiration - minWarning; } /** * Log the renewal of a client lease. * @param clw the wrapper for the client lease that was renewed */ void logRenewal(ClientLeaseWrapper clw) { if (!leases.contains(clw)) { // Some other thread must have removed this lease from // the set after renewal, don't bother logging change return; } final Object u = new UpdateClientLease(this, clw); store.update(u); } // Methods need to meet contract of LeasedResource // Inherit java doc from super type public void setExpiration(long newExpiration) { expiration = newExpiration; // Update the 2nd copy expiration2.set(expiration); } // Inherit java doc from super type public long getExpiration() { return expiration; } /** * This method is used by the client lease renewal threads to make * sure that the set associated with the lease they are renewing is * non-expired. Note, the method check expiration2, not expiration * so the renewal thread does not need to block on the set's lock. * @param now the current time in milliseconds since the beginning of * the epoch */ boolean ensureCurrent(long now) { return expiration2.ensureCurrent(now); } // Inherit java doc from super type public Uuid getCookie() { return ID; } /** * Return the <code>Uuid</code> for this set. */ Uuid getUuid() { return ID; } /** * If the passed registrationNumber number matches the * current registrationNumber for the passed event * clear the current registration and persist the change */ void definiteException(EventType type, RemoteEvent ev, long registrationNumber) { final boolean changed = type.clearListenerIfSequenceMatch(registrationNumber); if (changed) { // Need to log the change Object u; if (ev instanceof ExpirationWarningEvent) { u = new WarningEventRegistration(LeaseSet.this); } else { u = new FailureEventRegistration(LeaseSet.this); } store.update(u); } } /** * Returns whether to isolate renewal sets or batch leases across sets for * all lease renewal sets associated with this set's service. */ protected boolean isolateSets() { return normServerBaseImpl.isolateSets(); } /** * Returns a string representation of this object. */ public String toString() { return "LeaseSet" + ID; } // Inner classes /** * Utility class that holds and guards the second copy of our expiration * time. */ private static class ExpirationTime { private static final long serialVersionUID = 1L; /** * The expiration time in milliseconds since the beginning of the * epoch */ private long expirationTime; /** Simple constructor */ private ExpirationTime(long initVal) { expirationTime = initVal; } /** Update the current expiration time */ private synchronized void set(long newTime) { expirationTime = newTime; } /** * Return true if expiration time has not been reached * @param now the current time in milliseconds since the beginning of * the epoch */ private synchronized boolean ensureCurrent(long now) { return now <= expirationTime; } } // All the inner classes we use to log changes to a lease set /** * Class used to log changes to the set expiration time. */ static class ChangeSetExpiration extends LeaseSetOperation { private static final long serialVersionUID = 1L; /** * Updated expiration time * @serial */ private long expiration; /** * Simple constructor * @param set that changed * @param expiration the new expiration time */ ChangeSetExpiration(LeaseSet set, long expiration) { super(set.getUuid()); this.expiration = expiration; } // Inherit java doc from super type void apply(LeaseSet set) { set.setExpiration(expiration); } } /** * Class used to log adding or updating a client lease to the set */ private static class UpdateClientLease extends LeaseSetOperation { private static final long serialVersionUID = 1L; /** * Wrapped version of client lease * @serial */ private ClientLeaseWrapper clw; /** * Simple constructor * @param set that changed * @param clw Wrapped client lease */ private UpdateClientLease(LeaseSet set, ClientLeaseWrapper clw) { super(set.getUuid()); this.clw = clw; } // Inherit java doc from super type void apply(LeaseSet set) { set.replace(clw); } } /** * Class used to log the removal of a client lease from the set */ private static class RemoveClientLease extends LeaseSetOperation { private static final long serialVersionUID = 1L; /** * Client lease to be removed * @serial */ private ClientLeaseWrapper clw; /** * Simple constructor * @param set that changed * @param clw Wrapped client lease */ private RemoveClientLease(LeaseSet set, ClientLeaseWrapper clw) { super(set.getUuid()); this.clw = clw; } // Inherit java doc from super type void apply(LeaseSet set) { set.leases.remove(clw); } } /** * Class used to log a renewal failure */ private static class RenewalFailure extends RemoveClientLease { private static final long serialVersionUID = 1L; /** * Event ID of the corresponding renewal failure event (if any) * @serial */ private long evID; /** * Simple constructor * @param set that changed * @param clw Wrapped client lease * @param evID event ID of the renewal event that was sent * to mark this renewal failure */ private RenewalFailure(LeaseSet set, ClientLeaseWrapper clw, long evID) { super(set, clw); this.evID = evID; } // Inherit java doc from super type void apply(LeaseSet set) { super.apply(set); set.failureEventType.setLastSequenceNumber(evID); } } /** * Class used to log changes to warning event registrations */ private static class WarningEventRegistration extends LeaseSetOperation { private static final long serialVersionUID = 1L; /** * Warning time associated with warning event registration * @serial */ private long warningTime; /** * EventType object that resulted from registration change * @serial */ private EventType registration; /** * Simple constructor * @param set that changed */ private WarningEventRegistration(LeaseSet set) { super(set.getUuid()); warningTime = set.minWarning; registration = set.warningEventType; } // Inherit java doc from super type void apply(LeaseSet set) throws StoreException { set.minWarning = warningTime; set.warningEventType = registration; } } /** * Class used to log changes to failure event registrations */ private static class FailureEventRegistration extends LeaseSetOperation { private static final long serialVersionUID = 1L; /** * EventType object that resulted from registration change * @serial */ private EventType registration; /** * Simple constructor * @param set that changed */ private FailureEventRegistration(LeaseSet set) { super(set.getUuid()); registration = set.failureEventType; } // Inherit java doc from super type void apply(LeaseSet set) throws StoreException { set.failureEventType = registration; } } }