blob: 88e9fdff37070224426e208f8865955270b7f142 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.server;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.rmi.Remote;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.security.AccessControlException;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.net.PortRange;
import org.eclipse.statet.rj.RjException;
import org.eclipse.statet.rj.data.RObjectFactory;
@NonNullByDefault
public class RjsComConfig {
public static final String RJ_COM_S2C_ID_PROPERTY_ID= "rj.com.s2c.id";
public static final String RJ_DATA_STRUCTS_LISTS_MAX_LENGTH_PROPERTY_ID= "rj.data.structs.lists.max_length";
public static final String RJ_DATA_STRUCTS_ENVS_MAX_LENGTH_PROPERTY_ID= "rj.data.structs.envs.max_length";
private static final Map<String, Object> PROPERTIES= new ConcurrentHashMap<>();
public static interface PathResolver {
Path resolve(Remote client, String pathString) throws InvalidPathException, RjException;
}
public static void setServerPathResolver(final RjsComConfig.PathResolver resolver) {
BinExchange.gSPathResolver= resolver;
}
public static int registerClientComHandler(final ComHandler handler) {
final int id= MainCmdS2CList.gComHandlers.put(nonNullAssert(handler));
if (id < 0xffff) {
return id;
}
MainCmdS2CList.gComHandlers.remove(id);
throw new UnsupportedOperationException("Too much open clients");
}
public static void unregisterClientComHandler(final int id) {
MainCmdS2CList.gComHandlers.remove(id);
}
/**
* Registers an additional RObject factory
*
* Factory registration is valid for the current VM.
*/
public static final void registerRObjectFactory(final String id, final RObjectFactory factory) {
if (id == null || factory == null) {
throw new NullPointerException();
}
if (id.equals(DataCmdItem.DEFAULT_FACTORY_ID)) {
throw new IllegalArgumentException();
}
DataCmdItem.gFactories.put(id, factory);
}
/**
* Sets the default RObject factory
*
* Factory registration is valid for the current VM.
*/
public static final void setDefaultRObjectFactory(final RObjectFactory factory) {
DataCmdItem.gDefaultFactory= nonNullAssert(factory);
DataCmdItem.gFactories.put(DataCmdItem.DEFAULT_FACTORY_ID, factory);
}
public static final void setProperty(final String key, final Object value) {
PROPERTIES.put(key, value);
}
public static final @Nullable Object getProperty(final String key) {
return PROPERTIES.get(key);
}
private static final ThreadLocal<@Nullable RMIClientSocketFactory> gRMIClientSocketFactoriesInit= new ThreadLocal<>();
private static final ConcurrentHashMap<String, RMIClientSocketFactory> gRMIClientSocketFactories= new ConcurrentHashMap<>();
private static RMIClientSocketFactory getSystemRMIClientSocketFactory() {
RMIClientSocketFactory factory= RMISocketFactory.getSocketFactory();
if (factory == null) {
factory= RMISocketFactory.getDefaultSocketFactory();
}
return factory;
}
private static final class RjRmiClientSocketFactory implements RMIClientSocketFactory, Externalizable {
private static final long serialVersionUID= -2470426070934072117L;
private static String getLocalHostName() {
try {
return InetAddress.getLocalHost().getCanonicalHostName();
}
catch (final UnknownHostException e) {}
catch (final ArrayIndexOutOfBoundsException e) { /* JVM bug */ }
return "unknown";
}
private String id;
private @Nullable RMIClientSocketFactory resolvedFactory;
public RjRmiClientSocketFactory(final String init) {
final StringBuilder sb= new StringBuilder(init);
sb.append(getLocalHostName());
sb.append('/').append(System.nanoTime()).append('/').append(Math.random());
this.id= sb.toString();
}
/** for Externalizable */
@SuppressWarnings("null")
public RjRmiClientSocketFactory() {
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeUTF(this.id);
}
@Override
public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
this.id= in.readUTF();
}
@Override
public Socket createSocket(final @Nullable String host, final int port) throws IOException {
RMIClientSocketFactory factory= null;
factory= gRMIClientSocketFactoriesInit.get();
if (factory != null) {
this.resolvedFactory= factory;
gRMIClientSocketFactories.put(this.id, factory);
}
else {
factory= this.resolvedFactory;
if (factory == null) {
factory= gRMIClientSocketFactories.get(this.id);
if (factory != null) {
this.resolvedFactory= factory;
}
else {
factory= getSystemRMIClientSocketFactory();
}
}
}
return factory.createSocket(host, port);
}
@Override
public int hashCode() {
return this.id.hashCode();
}
@Override
public boolean equals(final @Nullable Object obj) {
return (this == obj
|| (obj instanceof RjRmiClientSocketFactory
&& this.id.equals(((RjRmiClientSocketFactory) obj).id) ));
}
}
private final static Random RANDOM= new Random();
private static final class RjRmiServerSocketFactory implements RMIServerSocketFactory {
private final RMIServerSocketFactory factory;
private final PortRange portRange;
private volatile int lastPortNumber;
public RjRmiServerSocketFactory(final RMIServerSocketFactory factory,
final PortRange portRange) {
this.factory= factory;
this.portRange= portRange;
}
private ServerSocket createSocket(final int port) throws IOException {
final ServerSocket socket= this.factory.createServerSocket(port);
this.lastPortNumber= port;
return socket;
}
private static int nextInt(int r, final int l) {
if (l < 10) {
r++;
}
else {
r+= 1 + RANDOM.nextInt(l / 4);
}
return (r < l) ? r : (r - l);
}
@Override
public ServerSocket createServerSocket(final int port) throws IOException {
if (port == 0) {
IOException e0= null;
int total= 0;
int base= this.lastPortNumber;
if (base != 0) {
base++;
final int iCount= Math.min(5, this.portRange.getMax() - base + 1);
for (int i= 0; i < iCount; i++) {
try {
return createSocket(base + i);
}
catch (final IOException e) {
e0= e;
}
}
total+= iCount;
}
base= this.portRange.getMin();
if (this.portRange.getLength() >= 100) {
final int l= this.portRange.getLength() / 10;
int r= RANDOM.nextInt(l);
final int iCount= Math.min(20, l);
for (int i= 0; i < iCount; i++) {
try {
return createSocket(base + r * 10);
}
catch (final IOException e) {
e0= e;
r= nextInt(r, l);
}
}
total+= iCount;
}
{ final int l= this.portRange.getLength();
int r= RANDOM.nextInt(l);
final int iCount= Math.min(50 - total, l);
for (int i= 0; i < iCount; i++) {
try {
return createSocket(base + r);
}
catch (final IOException e) {
e0= e;
r= nextInt(r, l);
}
}
total+= iCount;
}
throw new IOException("Failed to create server socket. No port available in range " + this.portRange.getText() + "?", e0);
}
else {
return this.factory.createServerSocket(port);
}
}
}
private static final String RMISERVER_CLIENTSOCKET_FACTORY_KEY= "org.eclipse.statet.rj.rmi.disableSocketFactory"; //$NON-NLS-1$
private static final String RMISERVER_SERVERSOCKET_PORT_RANGE_KEY= "org.eclipse.statet.rj.server.TcpPort"; //$NON-NLS-1$
private final static boolean RMISERVER_CLIENTSOCKET_FACTORY_ENABLED;
private final static @Nullable PortRange RMISERVER_SERVERSOCKET_PORT_RANGE;
private @Nullable static RMIClientSocketFactory RMISERVER_CLIENTSOCKET_CUSTOM_FACTORY;
static {
{ boolean enabled= true;
try {
if ("true".equalsIgnoreCase(System.getProperty(RMISERVER_CLIENTSOCKET_FACTORY_KEY))) {
enabled= false;
}
}
catch (final AccessControlException e) { // in RMI registry
}
RMISERVER_CLIENTSOCKET_FACTORY_ENABLED= enabled;
}
{ PortRange range= null;
try {
String s= System.getProperty(RMISERVER_SERVERSOCKET_PORT_RANGE_KEY);
if (s == null) {
s= System.getProperty(RMISERVER_SERVERSOCKET_PORT_RANGE_KEY + "Range"); //$NON-NLS-1$
}
if (s != null) {
range= PortRange.valueOf(s);
}
}
catch (final NumberFormatException e) {}
catch (final AccessControlException e) { // in RMI registry
}
RMISERVER_SERVERSOCKET_PORT_RANGE= range;
}
}
public static synchronized final @Nullable RMIClientSocketFactory getRMIServerClientSocketFactory(
final boolean isSsl) {
RMIClientSocketFactory factory;
if (isSsl) {
factory= new SslRMIClientSocketFactory();
}
else if (RMISERVER_CLIENTSOCKET_FACTORY_ENABLED) {
factory= RMISERVER_CLIENTSOCKET_CUSTOM_FACTORY;
if (factory == null) {
factory= new RjRmiClientSocketFactory("S/");
RMISERVER_CLIENTSOCKET_CUSTOM_FACTORY= factory;
}
}
else {
factory= null;
}
return factory;
}
public static synchronized final @Nullable RMIServerSocketFactory getRMIServerServerSocketFactory(
final boolean isSsl) {
RMIServerSocketFactory factory;
if (isSsl) {
factory= new SslRMIServerSocketFactory(null, null, true);
}
else {
factory= RMISocketFactory.getDefaultSocketFactory();
}
if (RMISERVER_SERVERSOCKET_PORT_RANGE != null) {
factory= new RjRmiServerSocketFactory(factory, RMISERVER_SERVERSOCKET_PORT_RANGE);
}
return factory;
}
public static final void setRMIClientSocketFactory(@Nullable RMIClientSocketFactory factory) {
if (factory == null) {
factory= getSystemRMIClientSocketFactory();
}
gRMIClientSocketFactoriesInit.set(factory);
}
public static final void clearRMIClientSocketFactory() {
gRMIClientSocketFactoriesInit.set(null);
}
}