/* $HeadURL:: $
* $Id$
*
* Copyright (c) 2009-2010 DuraSpace
* http://duraspace.org
*
* In collaboration with Topaz Inc.
* http://www.topazproject.org
*
* Licensed 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.akubraproject.rmi.server;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* RMI object exporter. Encapsulates the configuration needed to export an RMI object. It also
* holds a reference to the exported objects so that exported objects stays alive until
* un-exported.
*
* @author Pradeep Krishnan
*/
public class Exporter {
private static final Logger log = LoggerFactory.getLogger(Exporter.class);
/**
* The number of milliseconds to wait before re-attempting an unexport. Note that
* there is a bit of a back-off mechanism - each subsequent retry will use a multiple
* of this delay.
*/
public static final long RETRY_DELAY = 10;
private final int port;
private final RMIClientSocketFactory csf;
private final RMIServerSocketFactory ssf;
private final ScheduledExecutorService executor;
private final Set<Exportable> exportedObjects;
/**
* Creates a new Exporter object.
*
* @param port the port number on which the remote object receives calls (if port is zero, an
* anonymous port is chosen)
*/
public Exporter(int port) {
this(port, null, null);
}
/**
* Creates a new Exporter object.
*
* @param port the port number on which the remote object receives calls (if port is zero, an
* anonymous port is chosen)
* @param csf the client-side socket factory for making calls to the remote object
* @param ssf the server-side socket factory for receiving remote calls
*/
public Exporter(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) {
this.port = port;
this.csf = csf;
this.ssf = ssf;
this.executor = createExecutor();
exportedObjects = Collections.synchronizedSet(new HashSet<Exportable>());
}
private static ScheduledExecutorService createExecutor() {
return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "akubra-rmi-unexporter");
t.setPriority(Thread.MIN_PRIORITY);
t.setDaemon(true);
return t;
}
});
}
/**
* Gets the port where the exported objects listen.
*
* @return the port
*/
public int getPort() {
return port;
}
/**
* Exports the remote object to make it available to receive incoming calls using this
* configuration. Note that this exporter holds a reference to the exported object (not the
* stub, but the Exportable itself) so that the exported object remains in scope and does not
* get garbage collected. (This is something that the RMI spec claims to provide, but in reality
* does not. The sun.rmi.transport.ObjectTable only keeps weak references.) So this means, all
* exported objects using this exporter must be {@link #unexportObject un-exported} using this
* so that the reference held here is cleared. Failure to do so will result in memory leaks.
*
* @param object the remote object to be exported
*
* @return remote object stub
*
* @throws RemoteException if export fails
*/
public Remote exportObject(Exportable object) throws RemoteException {
Remote stub =
(csf == null) ? UnicastRemoteObject.exportObject(object, port)
: UnicastRemoteObject.exportObject(object, port, csf, ssf);
exportedObjects.add(object);
return stub;
}
/**
* Removes the remote object from the RMI runtime. If successful, the object can no longer
* accept incoming RMI calls. If the force parameter is true, the object is forcibly unexported
* even if there are pending calls to the remote object or the remote object still has calls in
* progress. If the force parameter is false, the object is only unexported when there are no
* pending or in progress calls to the object. If there are pending calls, the unexport is
* re-scheduled for later.
*
* @param object the remote object to be unexported
* @param force if true, unexports the object even if there are pending or in-progress calls; if
* false, only unexports the object when there are no pending or in-progress calls
*
* @throws NoSuchObjectException if the object is not exported
*/
public void unexportObject(Exportable object, boolean force)
throws NoSuchObjectException {
exportedObjects.remove(object);
unexportObject(object, force, 0);
}
private void unexportObject(Remote object, boolean force, int trial)
throws NoSuchObjectException {
if (!UnicastRemoteObject.unexportObject(object, force))
scheduleRetry(object, force, trial);
}
private void scheduleRetry(final Remote object, final boolean force, final int trial) {
if (log.isDebugEnabled())
log.debug("Scheduling retry #" + trial + " of unexport for " + object);
Runnable job =
new Runnable() {
public void run() {
try {
if (log.isDebugEnabled())
log.debug("Retrying unexport for " + object);
unexportObject(object, force, trial + 1);
} catch (NoSuchObjectException e) {
if (log.isDebugEnabled())
log.debug(object + " is already unexported", e);
}
}
};
executor.schedule(job, RETRY_DELAY * trial, TimeUnit.MILLISECONDS);
}
/**
* Gets the set of all exported objects.
*
* @return a copy of the exported objects.
*/
public Set<Exportable> getExportedObjects() {
synchronized (exportedObjects) {
return new HashSet<Exportable>(exportedObjects);
}
}
}