All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.river.norm.LeaseSet Maven / Gradle / Ivy

The newest version!
/*
 * 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 org.apache.river.norm;

import org.apache.river.landlord.LeasedResource;
import org.apache.river.norm.event.EventFactory;
import org.apache.river.norm.event.EventType;
import org.apache.river.norm.event.EventTypeGenerator;
import org.apache.river.norm.event.SendMonitor;
import org.apache.river.norm.proxy.*;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.security.AccessControlContext;
import java.util.ArrayList;
import java.util.Collections;
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.export.ProxyAccessor;
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 org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;

/**
 * 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.
 */
@AtomicSerial
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;

    /**
     * A table that maps client leases to client lease wrappers.
     */
    private volatile transient LeaseTable leaseTable;

    /**
     * Time before expiration to send expiration warning events
     * @serial
     */
    private volatile 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 volatile long warningSeqNum;

    /**
     * The event type for failure events
     * @serial
     */
    private EventType failureEventType;

    /**
     * PersistentStore that changes should be logged to.
     */
    private transient PersistentStore store;

    /**
     * The NormServerBaseImpl 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 PersistentStore that changes should be logged
     *              to
     * @param normServerBaseImpl the NormServerBaseImpl that 
     *              created this set
     */
    LeaseSet(Uuid ID,
	    EventTypeGenerator generator,
	    PersistentStore store,
	    NormServerBaseImpl normServerBaseImpl,
	    AccessControlContext context)
    {
	this.leases = new HashSet();
	this.store = store;
	this.normServerBaseImpl = normServerBaseImpl;
	this.ID = ID;

	// For completeness
        expiration = 0;
	expiration2 = new ExpirationTime(expiration);
	leaseTable = new LeaseTable();

	final SendMonitor sendMonitor = 
	    normServerBaseImpl.newSendMonitor(this);	

	try {
	    warningEventType = 
		generator.newEventType(sendMonitor, 
			LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID, context);
	    failureEventType =
		generator.newEventType(sendMonitor, 
			LeaseRenewalSet.RENEWAL_FAILURE_EVENT_ID, context);
	} catch (IOException e) {
	    // Because we are passing null for the listener we will
	    // never get an exception
	    throw new AssertionError();
	}
    }

    LeaseSet(GetArg arg) throws IOException {
	this(	arg.get("expiration", 0L),
		(Uuid) arg.get("ID", null),
		check((Set) arg.get("leases", null)),
		arg.get("minWarning", 0L),
		(EventType) arg.get("warningEventType", null),
		arg.get("warningSeqNum", 0L),
		(EventType) arg.get("failureEventType", null)
		);
	// Restore the 2nd copy
	expiration2 = new ExpirationTime(expiration);

	// Create lease table, but only populate after restoring lease wrapper
	// transient state
	leaseTable = new LeaseTable();
    }
    
    LeaseSet(long expiration,
	    Uuid ID,
	    Set leases,
	    long minWarning,
	    EventType warningEventType,
	    long warningSeqNum,
	    EventType failureEventType)
    {
	this.expiration = expiration;
	this.ID = ID;
	this.leases = new HashSet(leases);
	this.minWarning = minWarning;
	this.warningEventType = warningEventType;
	this.warningSeqNum = warningSeqNum;
	this.failureEventType = failureEventType;
    }
    
    private static Set check(Set leases) 
	    throws InvalidObjectException
    {
	try {
	    Set checkLeases = Collections.checkedSet(
		    new HashSet(leases.size()), ClientLeaseWrapper.class);
	    checkLeases.addAll(leases);
	    return checkLeases;
	} catch (ClassCastException e){
	    InvalidObjectException ex = new InvalidObjectException(
		    "leases must only contain instances of ClientLeaseWrapper");
	    ex.initCause(e);
	    throw ex;
	}
    }

    /**
     * Override readObject so we can restore expiration2
     */
    private void readObject(ObjectInputStream in) 
	throws IOException, ClassNotFoundException 
    {
	in.defaultReadObject();
	synchronized (this){
	    // 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 PersistentStore that changes should be 
     *        logged to
     * @param normServerBaseImpl the normServerBaseImpl 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,
				    AccessControlContext context)
    {
	this.normServerBaseImpl = normServerBaseImpl;
	this.store = store;

	final SendMonitor sendMonitor = 
	    normServerBaseImpl.newSendMonitor(this);	
	warningEventType.restoreTransientState(
		generator, sendMonitor, recoveredListenerPreparer, context);
	failureEventType.restoreTransientState(
		generator, sendMonitor, recoveredListenerPreparer, context);

	// 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 Set 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 listener is non-null return
     * an EventRegistration otherwise return null
     * @throws IOException if listener cannot be serialized 
     */
    EventRegistration setExpirationWarningListener(
	RemoteEventListener listener, 
	long                minWarning,
	MarshalledObject    handback)
        throws IOException     					       
    {
	synchronized (this){
	    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 listener is non-null return
     * an EventRegistration otherwise return null
     * @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 EventFactory that
     * generates ExpirationWarningEvents.
     */
    private static class WarningFactory implements EventFactory {
	private static final long serialVersionUID = 1L; // Not Serializable

	/** 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-null listener registered
     * for the expiration warning event.
     * 

* 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. */ synchronized 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 synchronized void setExpiration(long newExpiration) { expiration = newExpiration; // Update the 2nd copy expiration2.set(expiration); } // Inherit java doc from super type public synchronized 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 Uuid 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; // Not Serializable /** * 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 final 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 final 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 final 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 final long warningTime; /** * EventType object that resulted from registration change * @serial */ private final EventType registration; /** * Simple constructor * @param set that changed */ private WarningEventRegistration(LeaseSet set) { super(set.getUuid()); synchronized (set){ warningTime = set.minWarning; registration = set.warningEventType; } } // Inherit java doc from super type void apply(LeaseSet set) throws StoreException { synchronized (set){ 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 final 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; } } }