AFUNIXNaming.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.File;
import java.io.IOException;
import java.nio.file.Files;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.newsclub.net.unix.AFUNIXSocket;
/**
* The {@link AFUNIXSocket}-compatible equivalent of {@link Naming}. Use this class for accessing
* RMI registries that are reachable by {@link AFUNIXSocket}s.
*
* @author Christian Kohlschütter
*/
public final class AFUNIXNaming extends AFNaming {
private static final String PROP_RMI_SOCKET_DIR = "org.newsclub.net.unix.rmi.socketdir";
private static final File DEFAULT_SOCKET_DIRECTORY = new File(System.getProperty(
PROP_RMI_SOCKET_DIR, "/tmp"));
private boolean deleteRegistrySocketDir = false;
private final File registrySocketDir;
private final RMIClientSocketFactory defaultClientSocketFactory;
private final RMIServerSocketFactory defaultServerSocketFactory;
private final String socketPrefix;
private final String socketSuffix;
private AFUNIXNaming(File socketDir, int registryPort, String socketPrefix, String socketSuffix)
throws IOException {
super(registryPort, RMIPorts.RMI_SERVICE_PORT);
Objects.requireNonNull(socketDir);
this.registrySocketDir = socketDir;
this.socketPrefix = socketPrefix;
this.socketSuffix = socketSuffix;
this.defaultClientSocketFactory = null; // DefaultRMIClientSocketFactory.getInstance();
this.defaultServerSocketFactory = null; // DefaultRMIServerSocketFactory.getInstance();
}
/**
* Returns the directory where RMI sockets are stored by default.
*
* You can configure this location by setting the System property
* {@code org.newsclub.net.unix.rmi.socketdir} upon start.
*
* @return The directory.
*/
public static File getDefaultSocketDirectory() {
return DEFAULT_SOCKET_DIRECTORY;
}
/**
* Returns a new private instance that resides in a custom location, to avoid any collisions with
* existing instances.
*
* @return The private {@link AFNaming} instance.
* @throws IOException if the operation fails.
*/
public static AFUNIXNaming newPrivateInstance() throws IOException {
File tmpDir = Files.createTempDirectory("junixsocket-").toFile();
if (!tmpDir.canWrite()) {
throw new IOException("Could not create temporary directory: " + tmpDir);
}
AFUNIXNaming instance = getInstance(tmpDir, RMIPorts.DEFAULT_REGISTRY_PORT);
synchronized (instance) {
instance.deleteRegistrySocketDir = true;
}
return instance;
}
/**
* Returns the default instance of {@link AFUNIXNaming}. Sockets are stored in
* <code>java.io.tmpdir</code>.
*
* @return The default instance.
* @throws IOException if the operation fails.
*/
public static AFUNIXNaming getInstance() throws IOException {
return getInstance(DEFAULT_SOCKET_DIRECTORY, RMIPorts.DEFAULT_REGISTRY_PORT);
}
/**
* Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
* under the same, given directory.
*
* @param socketDir The directory to store sockets in.
* @return The instance.
* @throws RemoteException if the operation fails.
*/
public static AFUNIXNaming getInstance(final File socketDir) throws RemoteException {
return getInstance(socketDir, RMIPorts.DEFAULT_REGISTRY_PORT);
}
/**
* Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
* under the same, given directory.
*
* A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above
* {@code 100000}.
*
* @param socketDir The directory to store sockets in.
* @param registryPort The registry port. Should be above {@code 100000}.
* @return The instance.
* @throws RemoteException if the operation fails.
*/
public static AFUNIXNaming getInstance(File socketDir, final int registryPort)
throws RemoteException {
return getInstance(socketDir, registryPort, null, null);
}
/**
* Returns a {@link AFUNIXNaming} instance which support several socket files that can be stored
* under the same, given directory.
*
* A custom "registry port" can be specified. Typically, AF-UNIX specific ports should be above
* {@code 100000}.
*
* @param socketDir The directory to store sockets in.
* @param registryPort The registry port. Should be above {@code 100000}.
* @param socketPrefix A string to be inserted at the beginning of each socket filename, or
* {@code null}.
* @param socketSuffix A string to be added at the end of each socket filename, or {@code null}.
* @return The instance.
* @throws RemoteException if the operation fails.
*/
public static AFUNIXNaming getInstance(File socketDir, final int registryPort,
String socketPrefix, String socketSuffix) throws RemoteException {
return AFNaming.getInstance(registryPort, new AFUNIXNamingProvider(socketDir, socketPrefix,
socketSuffix));
}
private static final class AFUNIXNamingProvider implements AFNamingProvider<AFUNIXNaming> {
private final File socketDir;
private final String socketPrefix;
private final String socketSuffix;
public AFUNIXNamingProvider(File socketDir, String socketPrefix, String socketSuffix)
throws RemoteException {
try {
this.socketDir = socketDir.getCanonicalFile();
} catch (IOException e) {
throw new RemoteException(e.getMessage(), e);
}
this.socketPrefix = socketPrefix == null ? AFUNIXRMISocketFactory.DEFAULT_SOCKET_FILE_PREFIX
: socketPrefix;
this.socketSuffix = socketSuffix == null ? AFUNIXRMISocketFactory.DEFAULT_SOCKET_FILE_SUFFIX
: socketSuffix;
}
@Override
public AFUNIXNaming newInstance(int port) throws IOException {
return new AFUNIXNaming(socketDir, port, socketPrefix, socketSuffix); // NOPMD
}
}
/**
* Returns an {@link AFUNIXNaming} instance which only supports one file. (Probably only useful
* when you want/can access the exported {@link UnicastRemoteObject} directly)
*
* @param socketFile The socket file.
* @return The instance.
* @throws IOException if the operation fails.
*/
public static AFNaming getSingleFileInstance(final File socketFile) throws IOException {
return getInstance(socketFile, RMIPorts.PLAIN_FILE_SOCKET);
}
@Override
public AFUNIXRMISocketFactory getSocketFactory() {
return (AFUNIXRMISocketFactory) super.getSocketFactory();
}
@Override
public AFRegistry getRegistry() throws RemoteException {
return getRegistry(0, TimeUnit.SECONDS);
}
@Override
public AFUNIXRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteException {
return (AFUNIXRegistry) super.getRegistry(timeout, unit);
}
@Override
public AFUNIXRegistry getRegistry(boolean create) throws RemoteException {
return (AFUNIXRegistry) super.getRegistry(create);
}
@Override
public AFUNIXRegistry createRegistry() throws RemoteException {
return (AFUNIXRegistry) super.createRegistry();
}
/**
* Returns the socket file which is used to control the RMI registry.
*
* The file is usually in the directory returned by {@link #getRegistrySocketDir()}.
*
* @return The directory.
*/
public File getRegistrySocketFile() {
return getSocketFactory().getFile(getRegistryPort());
}
@Override
protected AFUNIXRMISocketFactory initSocketFactory() throws IOException {
return new AFUNIXRMISocketFactory(this, registrySocketDir, defaultClientSocketFactory,
defaultServerSocketFactory, socketPrefix, socketSuffix);
}
@Override
protected AFUNIXRegistry newAFRegistry(Registry impl) throws RemoteException {
return new AFUNIXRegistry(this, impl);
}
@Override
protected AFRegistry openRegistry(long timeout, TimeUnit unit) throws RemoteException {
File socketFile = getRegistrySocketFile();
if (!socketFile.exists()) {
if (waitUntilFileExists(socketFile, timeout, unit)) {
AFRegistry reg = getRegistry(false);
if (reg != null) {
return reg;
}
}
}
throw new ShutdownException("Could not find registry at " + getRegistrySocketFile());
}
private boolean waitUntilFileExists(File f, long timeout, TimeUnit unit) {
long timeWait = unit.toMillis(timeout);
try {
while (timeWait > 0 && !f.exists()) {
Thread.sleep(Math.min(50, timeWait));
timeWait -= 50;
}
} catch (InterruptedException e) {
// ignored
}
return f.exists();
}
private synchronized void deleteSocketDir() {
if (deleteRegistrySocketDir && registrySocketDir != null) {
try {
Files.delete(registrySocketDir.toPath());
} catch (IOException e) {
// ignore
}
}
}
@Override
protected void shutdownRegistryFinishingTouches() {
deleteSocketDir();
}
/**
* Returns the directory in which sockets used by this registry are located.
*
* @return The directory.
*/
public File getRegistrySocketDir() {
return registrySocketDir;
}
@Override
protected void initRegistryPrerequisites() throws ServerException {
if (registrySocketDir != null && !registrySocketDir.mkdirs() && !registrySocketDir
.isDirectory()) {
throw new ServerException("Cannot create socket directory:" + registrySocketDir);
}
}
}