/*
* 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 com.sun.jini.proxy.ConstrainableProxyUtil;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseMap;
import net.jini.core.lease.LeaseMapException;
/**
* Class that wraps LeaseMap created by client Leases. Provides hooks
* for synchronization and data associated with each client lease while
* allowing us to use <code>LeaseRenewalManager</code>. Objects of this
* class are returned by <code>createLeaseMap</code> calls made on
* <code>ClientLeaseWrapper</code> objects that are not deformed. <p>
*
* This class only allows as keys ClientLeaseWrappers that are non-deformed.
* Internally the mapping from ClientLeaseWrappers to longs is held in two
* Maps. The first is a LeaseMap that is created by the client lease
* associated with the first ClientLeaseWrapper added to the set. The second
* is a Map from client leases to the ClientLeaseWrappers that wrap them.
*
* @author Sun Microsystems, Inc.
* @see ClientLeaseWrapper
*/
class ClientLeaseMapWrapper extends AbstractMap implements LeaseMap {
private static final long serialVersionUID = 1L;
/** Methods for converting lease constraints to lease map constraints. */
private static final Method[] leaseToLeaseMapMethods;
static {
try {
Method cancel =
Lease.class.getMethod("cancel", new Class[] { });
Method cancelAll =
LeaseMap.class.getMethod("cancelAll", new Class[] { });
Method renew =
Lease.class.getMethod("renew", new Class[] { long.class });
Method renewAll =
LeaseMap.class.getMethod("renewAll", new Class[] { });
leaseToLeaseMapMethods = new Method[] {
cancel, cancelAll, renew, renewAll };
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
/**
* LeaseMap created by client lease, mapping client leases to Long
* expiration times.
*
* @serial
*/
private final LeaseMap clientLeaseMap;
/**
* Map from client leases to ClientLeaseWrapper instances.
*
* @serial
*/
private final Map wrapperMap = new HashMap();
/**
* Retain initial wrapper so canContainKey can use it to determine if
* a specified lease may be added.
*
* @serial
*/
private final ClientLeaseWrapper example;
/**
* Create a ClientLeaseMapWrapper object that will hold
* the specified client Lease.
* @param wrapper a wrapper for the lease that wants to be renewed
* @param duration the duration to associate with wrapper
* @throws IllegalArgumentException if wrapper is deformed
*/
ClientLeaseMapWrapper(ClientLeaseWrapper wrapper, long duration) {
final Lease clientLease = wrapper.getClientLease();
if (clientLease == null) {
throw new IllegalArgumentException("Wrapper cannot be deformed");
}
LeaseMap leaseMap = clientLease.createLeaseMap(duration);
if (clientLease instanceof RemoteMethodControl &&
leaseMap instanceof RemoteMethodControl)
{
leaseMap = (LeaseMap)
((RemoteMethodControl) leaseMap).setConstraints(
ConstrainableProxyUtil.translateConstraints(
((RemoteMethodControl) clientLease).getConstraints(),
leaseToLeaseMapMethods));
}
clientLeaseMap = leaseMap;
wrapperMap.put(clientLease, wrapper);
example = wrapper;
}
// inherit javadoc
public void cancelAll() {
throw new UnsupportedOperationException(
"ClientLeaseMapWrapper.cancelAll: " +
"LRS should not being canceling client leases");
}
/**
* For each lease in the map, call failedRenewal
*/
private void applyException(Throwable t) {
for (Iterator i=wrapperMap.values().iterator(); i.hasNext(); ) {
final ClientLeaseWrapper clw = (ClientLeaseWrapper) i.next();
clw.failedRenewal(t);
}
}
// inherit javadoc
// This method assumes that none of the leases in the map are
// also being renewed by some other thread.
public void renewAll() throws LeaseMapException, RemoteException {
LeaseMapException lme = null;
Map newExceptionMap = null;
// Iterate over the wrappers, causing the appropriate exceptions
// for the ones who's sets have expired.
final long now = System.currentTimeMillis();
for (Iterator i=wrapperMap.values().iterator(); i.hasNext(); ) {
final ClientLeaseWrapper clw = (ClientLeaseWrapper) i.next();
if (!clw.ensureCurrent(now)) {
// Map an exception to this lease and drop from the map
// If necessary create newExceptionMap
if (newExceptionMap == null)
newExceptionMap = new HashMap(wrapperMap.size());
// Add to the newExceptionMap
newExceptionMap.put(clw,
LRMEventListener.EXPIRED_SET_EXCEPTION);
// Drop from both the wrapper and inner map
i.remove();
clientLeaseMap.remove(clw.getClientLease());
// Note, we don't call failedRenewal() because the set is
// dead so logging changes is pointless. Besides, there
// is no change to log.
}
}
// If the map is now empty don't bother calling renewAll()
if (clientLeaseMap.isEmpty()) {
if (newExceptionMap == null)
return;
throw new LeaseMapException("Expired Sets", newExceptionMap);
}
try {
clientLeaseMap.renewAll();
} catch (LeaseMapException e) {
lme = e;
} catch (RemoteException e) {
applyException(e);
throw e;
} catch (Error e) {
applyException(e);
throw e;
} catch (RuntimeException e) {
applyException(e);
throw e;
}
// For each Lease still in the map we need to update the wrapper
for (Iterator i=clientLeaseMap.keySet().iterator(); i.hasNext(); ) {
final Lease cl = (Lease) i.next();
final ClientLeaseWrapper clw =
(ClientLeaseWrapper) wrapperMap.get(cl);
clw.successfulRenewal();
}
// If there were no errors just return
if (lme == null && newExceptionMap == null)
return;
// If the renewAll() threw a LeaseMapException we have to
// remove the problem leases from the wrapper and place
// them in newExceptionMap
if (lme != null) {
final Map exceptionMap = lme.exceptionMap;
// Create the newExceptionMap if we don't have one
if (newExceptionMap == null)
newExceptionMap = new HashMap(exceptionMap.size());
// Copy each lease out of the exception's map into newExceptionMap,
// also remove these leases from the wrapper and get the
// failure logged
for (Iterator i = exceptionMap.entrySet().iterator();
i.hasNext(); )
{
final Map.Entry e = (Map.Entry) i.next();
final Lease cl = (Lease) e.getKey();
final Throwable t = (Throwable) e.getValue();
final ClientLeaseWrapper clw =
(ClientLeaseWrapper) wrapperMap.remove(cl);
i.remove();
clw.failedRenewal(t);
newExceptionMap.put(clw, t);
}
}
// If necessary throw a LeaseMapException
if (newExceptionMap != null) {
throw new LeaseMapException(
(lme == null) ? "Expired Sets" : lme.getMessage(),
newExceptionMap);
}
return;
}
// inherit javadoc
public boolean canContainKey(Object key) {
return key instanceof Lease &&
example.canBatch((Lease) key);
}
/**
* Check that the key is valid for this map, if it is return the client
* lease, if not throw IllegalArgumentException.
*/
private Lease checkKey(Object key) {
if (canContainKey(key))
return ((ClientLeaseWrapper) key).getClientLease();
throw new IllegalArgumentException(
"key is not valid for this LeaseMap");
}
/** Check that the value is a Long. */
private static void checkValue(Object value) {
if (!(value instanceof Long))
throw new IllegalArgumentException("value is not a Long");
}
// inherit javadoc
public boolean containsKey(Object key) {
final Lease cl = checkKey(key);
return clientLeaseMap.containsKey(cl);
}
// inherit javadoc
public boolean containsValue(Object value) {
checkValue(value);
return clientLeaseMap.containsValue(value);
}
// inherit javadoc
public Object get(Object key) {
final Lease cl = checkKey(key);
return clientLeaseMap.get(cl);
}
// inherit javadoc
public Object put(Object key, Object value) {
final Lease cl = checkKey(key);
checkValue(value);
// At this point we know key is a ClientLeaseWrapper
/*
* Since there is a 1:1 mapping between wrappers and client
* leases, and, once we get going, we never have two wrapper
* objects that != but are .equals(), if key is already in this
* map, then wrapperMap.put() will replace the existing copy of
* key with a == copy. This gives us the key non-replacement
* semantics Map.put() should have.
*/
wrapperMap.put(cl, key);
return clientLeaseMap.put(cl, value);
}
// inherit javadoc
public Object remove(Object key) {
final Lease cl = checkKey(key);
wrapperMap.remove(cl);
return clientLeaseMap.remove(cl);
}
// inherit javadoc
public void putAll(Map m) {
Iterator iter = m.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry e = (Map.Entry) iter.next();
put(e.getKey(), e.getValue());
}
}
// inherit javadoc
public void clear() {
clientLeaseMap.clear();
wrapperMap.clear();
}
// inherit javadoc
public boolean equals(Object o) {
return clientLeaseMap.equals(o); // XXX should sameDestination matter?
}
// inherit javadoc
public int hashCode() {
return clientLeaseMap.hashCode();
}
// inherit javadoc
public Set entrySet() {
return new EntrySet();
}
/*
* Classes that we use to implement entrySet()
*/
/**
* An implementation of Set backed by the ClientLeaseMapWrapper's
* mappings, which are from wrapperMap's values to clientLeaseMap
*/
private final class EntrySet extends AbstractSet {
// inherit javadoc
public Iterator iterator() {
return new EntryIterator();
}
/**
* If the passed object is a Map.Entry that is in the
* ClientMapWrapper return the client lease associated with it,
* otherwise return null
*/
private Lease getClientLease(Object o) {
if (!(o instanceof Map.Entry))
return null;
final Map.Entry e = (Map.Entry) o;
final Object eValue = e.getValue();
if (!(e.getKey() instanceof ClientLeaseWrapper) ||
!(eValue instanceof Long) ||
(eValue == null))
{
return null;
}
final ClientLeaseWrapper clw = (ClientLeaseWrapper) e.getKey();
// Note if clw is deformed this call will return null, this is
// the right thing since a deformed lease can't be in this map
return clw.getClientLease();
}
// inherit javadoc
public boolean contains(Object o) {
final Lease cl = getClientLease(o);
if (cl == null)
return false;
final Object eValue = ((Map.Entry) o).getValue();
final Object value = clientLeaseMap.get(cl);
if (value == null)
return false;
return value.equals(eValue);
}
// inherit javadoc
public boolean remove(Object o) {
final Lease cl = getClientLease(o);
if (cl == null)
return false;
final Object eValue = ((Map.Entry) o).getValue();
final Object value = clientLeaseMap.get(cl);
if (value == null || !value.equals(eValue))
return false;
// Use cl to remove the data from the clientLeaseMap
clientLeaseMap.remove(cl);
wrapperMap.remove(cl);
return true;
}
// inherit javadoc
public int size() {
return clientLeaseMap.size();
}
// inherit javadoc
public void clear() {
wrapperMap.clear();
clientLeaseMap.clear();
}
}
/** Our implementation of Map.Entry */
private final class Entry implements Map.Entry {
/** The key */
private final ClientLeaseWrapper key;
public Entry(ClientLeaseWrapper key) {
this.key = key;
}
public Object getKey() {
return key;
}
public Object getValue() {
return clientLeaseMap.get(key.getClientLease());
}
public Object setValue(Object value) {
checkValue(value);
return clientLeaseMap.put(key.getClientLease(), value);
}
public boolean equals(Object o) {
if (o instanceof Entry) {
final Entry that = (Entry) o;
return that.key.equals(key);
}
return false;
}
public int hashCode() {
return key.hashCode();
}
}
/**
* An implementation of Iterator backed by the ClientMapWrapper's mappings,
* which are from wrapperMap's values to clientLeaseMap
*/
private final class EntryIterator implements Iterator {
/** Iterator over the wrapperMap values */
private final Iterator iter;
/** Lease associated with the last value returned by next() */
private Lease last;
public EntryIterator() {
iter = wrapperMap.entrySet().iterator();
}
public boolean hasNext() {
return iter.hasNext();
}
public Object next() {
final Map.Entry e = (Map.Entry) iter.next();
last = (Lease) e.getKey();
return new Entry((ClientLeaseWrapper) e.getValue());
}
public void remove() {
// Use last to remove the data from the clientLeaseMap
clientLeaseMap.remove(last);
// use iter.remove() to remove from wrapperMap so we don't
// get a concurrent access exception
iter.remove();
}
}
}