/*
* Copyright 2008 Fedora Commons, Inc.
*
* 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.mulgara.util;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RemoteStub;
import java.rmi.server.UnicastRemoteObject;
import org.apache.log4j.Logger;
import org.mulgara.config.MulgaraConfig;
import org.neilja.net.interruptiblermi.InterruptibleRMISocketFactory;
/**
* A utility to centralize the port handling for RMI objects.
* This class is not set to handle different protocols. If this is needed, then the
* super constructor for {@link UnicastRemoteObject} with socket factories
* would need to be overridden.
*
* @created Sep 23, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public class Rmi extends UnicastRemoteObject {
/** Logger */
private final static Logger logger = Logger.getLogger(Rmi.class.getName());
/** Generation UID */
private static final long serialVersionUID = -8087526398171872888L;
/** Java system property used to set the default RMI client object port. */
public static final String CLIENT_OBJECT_PORT = "mulgara.rmi.objectPort";
/** Java system property used to enable/disable the use of interruptible RMI sessions. */
public static final String INTERRUPT = "mulgara.rmi.interrupt";
/** The default port used for exporting objects. */
protected static int defaultPort = 0;
/**
* Flag used to configure the enabling of interruptible RMI sessions by default.
* Initially set from the value of the {@link #INTERRUPT} system property, if present.
*/
protected static Boolean defaultInterrupt = null;
// Check if a system property has been set for the default port or interruptible RMI.
static {
String val = System.getProperty(CLIENT_OBJECT_PORT);
if (val != null) {
try {
defaultPort = Integer.parseInt(val);
} catch (NumberFormatException e) {
logger.warn("Unable to parse the client peer port for RMI: " + val);
}
}
val = System.getProperty(INTERRUPT);
if (val != null) {
defaultInterrupt = Boolean.valueOf(val);
}
}
/**
* Default constructor. Uses the default port.
* @throws RemoteException If the object could not be exported.
*/
protected Rmi() throws RemoteException {
super(defaultPort);
}
/**
* Constructor with a specified port.
* @param port A specified port. If 0 then a default port will be used.
* @throws RemoteException If the object could not be exported.
*/
protected Rmi(int port) throws RemoteException {
super(port == 0 ? defaultPort : port);
}
/**
* We don't want users using this method, since we cannot control the port, and we cannot
* control the return type if a port is specified through another method.
* @param obj The object to export.
* @return Not implemented.
* @throws RemoteException There was an error exporting the object.
*/
public static RemoteStub exportObject(Remote obj) throws RemoteException {
throw new UnsupportedOperationException("Use the export() method instead.");
}
/**
* Exports an object through RMI, using a known port if configured, or a random port otherwise.
* This will not create a default exporter if one does not exist.
* @param obj The object to export.
* @return An exported object.
* @throws RemoteException There was an error exporting the object.
*/
public static Remote export(Remote obj) throws RemoteException {
return export(obj, false);
}
/**
* Exports an object through RMI, enabling interruptible operations on the exported
* object if specified. If a known port is configured then it will be used to export the
* object, otherwise a random anonymous port will be chosen.
* @param obj The object to export.
* @param interruptible <tt>true</tt> to enable interruptible RMI operations on the exported object.
* @return An exported object.
* @throws RemoteException There was an error exporting the object.
*/
public static Remote export(Remote obj, boolean interruptible) throws RemoteException {
if (interruptible) {
InterruptibleRMISocketFactory sf = new InterruptibleRMISocketFactory();
return UnicastRemoteObject.exportObject(obj, defaultPort, sf, sf);
}
if (defaultPort == 0) return UnicastRemoteObject.exportObject(obj);
return UnicastRemoteObject.exportObject(obj, defaultPort);
}
/**
* Unexport an object from RMI.
* @param obj The object to unexport.
* @return <code>false</code> if the object could not be unexported. This may happen if it is
* still in use.
* @throws RemoteException There was an error exporting the object.
*/
public static boolean unexportObject(Remote obj) throws RemoteException {
return UnicastRemoteObject.unexportObject(obj, false);
}
/**
* Sets the default system behavior for enabling/disabling interruptible RMI operations,
* based on the <tt>RMIInterrupt</tt> property from the specified Mulgara XML config file.
* If the default behavior has already been set by the {@link #INTERRUPT} system property,
* then this method has no effect.
* @param config A Mulgara XML configuration.
*/
public static void configure(MulgaraConfig config) {
if (defaultInterrupt == null && config.hasRMIInterrupt()) {
defaultInterrupt = Boolean.valueOf(config.getRMIInterrupt());
}
}
/**
* Sets the port for the default exporter to use.
* @param port The port number to use.
*/
public static void setDefaultPort(int port) {
defaultPort = port;
}
/**
* Gets the port to use.
* @return The port number, or 0 if a random port is to be used.
*/
public static int getDefaultPort() {
return defaultPort;
}
/**
* Manually override the configured system behavior for interruptible RMI operations.
* @param interrupt <tt>true</tt> to enable interruptible RMI sessions where appropriate.
*/
public static void setDefaultInterrupt(boolean interrupt) {
defaultInterrupt = Boolean.valueOf(interrupt);
}
/**
* Get the configured system default enabled status of interruptible RMI operations.
* The order of precedence is as follows:
* <ol>
* <li>If the default has been set in code via the {@link #setDefaultInterrupt(boolean)}
* method, return that value.</li>
* <li>The value of the {@link #INTERRUPT} Java system property, if present.</li>
* <li>The value set by the Mulgara XML config file via the {@link #configure(MulgaraConfig)}
* method, if applicable.</li>
* <li>If the default has not been configured by any of the above means, return <tt>false</tt>.</li>
* </ol>
* @return <tt>true</tt> if interruptible RMI operations should be enabled by default.
*/
public static boolean getDefaultInterrupt() {
if (defaultInterrupt != null) return defaultInterrupt.booleanValue();
return false;
}
/**
* Determines whether the current thread is an RMI server thread that has been
* interrupted by the client. This method only gives useful results if the remote
* object in question was exported using the {@link #export(Remote, boolean)} method
* with the <tt>interruptible</tt> parameter set to <tt>true</tt>.
* @return <tt>true</tt> if the current thread is an RMI server thread, and the underlying
* socket has been closed by the client.
*/
public static boolean isInterrupted() {
return InterruptibleRMISocketFactory.isCurrentThreadRMIServer() &&
!InterruptibleRMISocketFactory.isCurrentRMIServerThreadSocketAlive();
}
/**
* Unexport this object from RMI.
* @return <code>false</code> if the object could not be unexported. This may happen if it is
* still in use.
* @throws RemoteException There was an error exporting the object.
*/
public boolean unexport() throws RemoteException {
return unexportObject(this);
}
/**
* Unexport an object from RMI.
* @param force If true, then unexport the object, even if it still in use.
* @return <code>false</code> if the object could not be unexported. This may happen if
* <var>force</var> is <code>false</code> and <code>obj</code> is still in use.
* @throws RemoteException There was an error exporting the object.
*/
public boolean unexport(boolean force) throws RemoteException {
return unexportObject(this, force);
}
}