AFRegistry.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* 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.newsclub.net.unix.rmi;
import java.io.IOException;
import java.rmi.AccessException;
import java.rmi.AlreadyBoundException;
import java.rmi.ConnectIOException;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteServer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.newsclub.net.unix.rmi.ShutdownHookSupport.ShutdownHook;
import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
/**
* A wrapper for RMI registries, both remote and local, to allow for a clean removal of bound
* resources upon shutdown.
*
* @author Christian Kohlschütter
*/
public abstract class AFRegistry implements Registry {
final RemoteCloseable<?> boundCloser;
private final Registry impl;
private final Map<String, Remote> bound = new HashMap<>();
private final AFNaming naming;
private final AtomicBoolean boundCloserExported = new AtomicBoolean(false);
private AFRMIService rmiService = null;
AFRegistry(AFNaming naming, Registry impl) {
this.naming = naming;
this.impl = impl;
this.boundCloser = new RemoteCloseable<Void>() {
@Override
public Void get() throws IOException {
return null;
}
@Override
public void close() throws IOException {
AFRegistry.this.forceUnexportBound();
}
};
if (isLocal()) {
ShutdownHookSupport.addWeakShutdownHook(new ShutdownHook() {
@Override
public void onRuntimeShutdown(Thread thread) {
forceUnexportBound();
}
});
}
}
/**
* Returns {@code true} if the wrapped Registry instance is a locally created
* {@link RemoteServer}.
*
* @return {@code true} if wrapped instance is a locally created {@link RemoteServer}.
* @see #isLocal()
*/
@Deprecated
public boolean isRemoteServer() {
return isLocal();
}
/**
* Returns {@code true} if the wrapped Registry instance is locally created.
*
* @return {@code true} if wrapped instance is locally created.
*/
public final boolean isLocal() {
return (impl instanceof RemoteServer);
}
/**
* Returns the {@link AFNaming} instance responsible for this registry.
*
* @return The {@link AFNaming} instance.
*/
@SuppressFBWarnings("EI_EXPOSE_REP")
public AFNaming getNaming() {
return naming;
}
@Override
public Remote lookup(String name) throws RemoteException, NotBoundException, AccessException {
return impl.lookup(name);
}
@Override
public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException,
AccessException {
impl.bind(name, RemoteObject.toStub(obj));
synchronized (bound) {
bound.put(name, obj);
}
checkBound();
}
@Override
public void unbind(String name) throws RemoteException, NotBoundException, AccessException {
impl.unbind(name);
synchronized (bound) {
bound.remove(name);
}
checkBound();
}
@Override
public void rebind(String name, Remote obj) throws RemoteException, AccessException {
impl.rebind(name, RemoteObject.toStub(obj));
synchronized (bound) {
bound.put(name, obj);
}
checkBound();
}
@Override
public String[] list() throws RemoteException, AccessException {
return impl == null ? new String[0] : impl.list();
}
/**
* Returns the remote reference bound to the specified <code>name</code> in this registry. If the
* reference has not been bound yet, repeated attempts to resolve it are made until the specified
* time elapses.
*
* @param name the name for the remote reference to look up
* @param timeout The timeout value.
* @param unit The timeout unit.
*
* @return a reference to a remote object
*
* @throws NotBoundException if <code>name</code> is not currently bound and couldn't be resolved
* in the specified time.
*
* @throws RemoteException if remote communication with the registry failed; if exception is a
* <code>ServerException</code> containing an <code>AccessException</code>, then the
* registry denies the caller access to perform this operation
*
* @throws AccessException if this registry is local and it denies the caller access to perform
* this operation
*
* @throws NullPointerException if <code>name</code> is <code>null</code>
*/
public Remote lookup(String name, long timeout, TimeUnit unit) throws NotBoundException,
RemoteException {
long timeWait = unit.toMillis(timeout);
Exception exFirst = null;
do {
try {
return impl.lookup(name);
} catch (NotBoundException | ConnectIOException | NoSuchObjectException e) {
if (exFirst == null) {
exFirst = e;
}
}
try {
Thread.sleep(Math.min(timeWait, 50));
} catch (InterruptedException e1) {
exFirst.addSuppressed(e1);
break;
}
timeWait -= 50;
} while (timeWait > 0);
if (exFirst instanceof NotBoundException) {
throw (NotBoundException) exFirst;
} else if (exFirst instanceof RemoteException) {
throw (RemoteException) exFirst;
} else {
throw new RemoteException("Lookup timed out");
}
}
void forceUnexportBound() {
final Map<String, Remote> map;
synchronized (bound) {
map = new HashMap<>(bound);
bound.clear();
}
try {
checkBound();
} catch (RemoteException e1) {
// ignore
}
for (Map.Entry<String, Remote> en : map.entrySet()) {
String name = en.getKey();
Remote obj = en.getValue();
if (obj == null) {
continue;
}
AFNaming.unexportObject(obj);
try {
unbind(name);
} catch (RemoteException | NotBoundException e) {
// ignore
}
}
try {
for (String list : list()) {
try {
unbind(list);
} catch (RemoteException | NotBoundException e) {
// ignore
}
}
} catch (RemoteException e) {
// ignore
}
AFNaming.unexportObject(this.impl);
AFNaming.unexportObject(this);
}
private void checkBound() throws RemoteException {
boolean empty;
synchronized (bound) {
empty = bound.isEmpty();
}
if (empty) {
if (boundCloserExported.compareAndSet(true, false)) {
AFRMIService service;
try {
service = getRMIService();
service.unregisterForShutdown(boundCloser);
} catch (NoSuchObjectException | NotBoundException e) {
return;
} finally {
AFNaming.unexportObject(boundCloser);
}
}
} else if (boundCloserExported.compareAndSet(false, true)) {
AFNaming.exportObject(boundCloser, naming.getSocketFactory());
AFRMIService service;
try {
service = getRMIService();
} catch (NotBoundException e) {
return;
}
service.registerForShutdown(boundCloser);
}
}
private AFRMIService getRMIService() throws RemoteException, NotBoundException {
if (rmiService == null) {
rmiService = naming.getRMIService(this);
}
return rmiService;
}
}