/*
Copyright (c) 2007 Health Market Science, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
You can contact Health Market Science at info@healthmarketscience.com
or at the following address:
Health Market Science
2700 Horizon Drive
Suite 200
King of Prussia, PA 19406
*/
package com.healthmarketscience.rmiio.exporter;
import java.rmi.RemoteException;
import com.healthmarketscience.rmiio.RemoteStreamServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for objects which manage exporting RemoteStreamServers.
* "Exporting" is the act of making a RemoteStreamServer available remotely
* via some RPC framework (such as RMI). This class allows the rmiio
* utilities to be used with any RPC framework by separating the server
* implementation from the RPC integration.
* <p>
* See the {@link #getInstance} method for details on how the default exporter
* can be specified as a system property.
* <p>
* RemoteStreamExporter implementations are expected to be thread-safe and
* reentrant after construction.
* <p>
* For some helper classes which may be useful for alternative RPC
* frameworks, see {@link RemoteStreamServerInvokerHelper},
* {@link RemoteInputStreamClientProxy}, and
* {@link RemoteOutputStreamClientProxy}.
*
* @author James Ahlborn
*/
public abstract class RemoteStreamExporter
{
protected static final Logger LOG = LoggerFactory.getLogger(RemoteStreamExporter.class);
/** system property used by {@link #getInstance} to determine which exporter
implementation to return */
public static final String EXPORTER_PROPERTY =
"com.healthmarketscience.rmiio.exporter";
/** name of the default exporter implementation returned by
{@link #getInstance} if none is specified via system property */
public static final String DEFAULT_EXPORTER_CLASS_NAME =
DefaultRemoteStreamExporter.class.getName();
/** RemoteStreamExporter instance returned by {@link #getInstance}, created
once, on demand */
private static RemoteStreamExporter _INSTANCE = null;
protected RemoteStreamExporter() {
}
/**
* Returns the default RemoteStreamExporter to use.
* @return the default RemoteStreamExporter to use, either specified by the
* system property {@link #EXPORTER_PROPERTY} or an instance of
* {@link #DEFAULT_EXPORTER_CLASS_NAME}. The exporter is
* instantiated once, on demand, and returned thereafter.
*/
public static synchronized RemoteStreamExporter getInstance() {
if(_INSTANCE == null) {
String exporterClassName = System.getProperty(
EXPORTER_PROPERTY, DEFAULT_EXPORTER_CLASS_NAME);
LOG.info("Using stream exporter " + exporterClassName);
try {
_INSTANCE = (RemoteStreamExporter)
Class.forName(exporterClassName).newInstance();
} catch(Exception e) {
throw new IllegalArgumentException(
"could not instantiate exporter " + exporterClassName, e);
}
}
return _INSTANCE;
}
/**
* Exports the given stream server via the desired RPC framework and returns
* the "remote" instance (often some sort of serializable stub object). The
* given stream instance should now be reachable from a remote call.
* @return the remote stub used for interacting with this stream instance
* from a remote client
* @throws RemoteException if the stream instance could not be exported
*/
public <StreamType,
StreamServerType extends RemoteStreamServer<?,StreamType>> StreamType export(
StreamServerType server)
throws RemoteException
{
synchronized(server) {
if(LOG.isDebugEnabled()) {
LOG.debug("Exporting remote object " + server);
}
// first, do the actual export (if the exportImpl call fails, we have to
// assume the object was not successufully exported)
Object stubObj = exportImpl(server);
boolean exportProcessed = false;
StreamType stub = null;
try {
// cast the stub to the correct type
stub = server.getRemoteClass().cast(stubObj);
// let the stream do stuff if necessary
server.exported(this);
// all good!
exportProcessed = true;
} finally {
if(!exportProcessed) {
// bailout!
unexport(server);
}
}
return stub;
}
}
/**
* Unexports the given previously exported stream server from the desired
* RPC framework. The given stream instance will no longer be reachable
* from a remote call.
*/
public void unexport(RemoteStreamServer<?,?> server)
{
synchronized(server) {
try {
if(LOG.isDebugEnabled()) {
LOG.debug("Unexporting remote object " + server);
}
// do the actual unexport
unexportImpl(server);
} catch(Exception e) {
if(LOG.isDebugEnabled()) {
LOG.debug("Unexporting failed! for " + server, e);
}
// whatever...
}
}
}
/**
* Called by {@link #export} to do the actual export work for the relevant
* RPC framework. This method will be called synchronized on the given
* stream instance, so it will not overlap an {@link #unexport} call for the
* same instance.
* <p>
* Note, RemoteStreamServer implements Unreferenced, which is an rmi
* interface used to clean up servers which have lost their clients. RPC
* frameworks which export remote streams should attempt to handle abnormal
* client termination, and are encouraged to make use of the Unreferenced
* interface to shutdown an orphaned stream server.
*
* @return the remote stub, which should be an instance of the remote
* interface of this server
* @throws RemoteException if the stream instance could not be exported
*/
protected abstract Object exportImpl(RemoteStreamServer<?,?> server)
throws RemoteException;
/**
* Called by {@link #unexport} to do the actual unexport work for the
* relevant RPC framework. This method will be called synchronized on the
* given stream instance, so it will not overlap an {@link #export} call for
* the same instance. This method call is allowed break existing
* connections to this stream instance. Any exceptions thrown will be
* logged, but otherwise ignored.
* @throws Exception if the unexport failed
*/
protected abstract void unexportImpl(RemoteStreamServer<?,?> server)
throws Exception;
}