| /*=============================================================================# |
| # Copyright (c) 2009, 2022 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); |
| } |
| |
| } |