| /*=============================================================================# |
| # Copyright (c) 2008, 2019 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.srv; |
| |
| import java.io.StreamCorruptedException; |
| import java.lang.reflect.Constructor; |
| import java.net.MalformedURLException; |
| import java.net.UnknownHostException; |
| import java.rmi.AlreadyBoundException; |
| import java.rmi.NoSuchObjectException; |
| import java.rmi.NotBoundException; |
| import java.rmi.Remote; |
| import java.rmi.RemoteException; |
| import java.rmi.UnmarshalException; |
| import java.rmi.server.RMIClientSocketFactory; |
| import java.rmi.server.RMIServerSocketFactory; |
| import java.rmi.server.UnicastRemoteObject; |
| import java.security.Policy; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| |
| import org.eclipse.statet.jcommons.rmi.RMIAddress; |
| import org.eclipse.statet.jcommons.rmi.RMIRegistry; |
| |
| import org.eclipse.statet.rj.RjException; |
| import org.eclipse.statet.rj.RjInvalidConfigurationException; |
| import org.eclipse.statet.rj.server.RjsComConfig; |
| import org.eclipse.statet.rj.server.Server; |
| import org.eclipse.statet.rj.server.srv.engine.SrvEngineServer; |
| import org.eclipse.statet.rj.server.srvext.ServerAuthMethod; |
| import org.eclipse.statet.rj.server.srvext.ServerRuntimePlugin; |
| import org.eclipse.statet.rj.server.util.ServerUtils; |
| import org.eclipse.statet.rj.server.util.ServerUtils.ArgKeyValue; |
| |
| |
| public class RMIServerControl extends ServerControl { |
| |
| |
| public static final int EXIT_REGISTRY_PROBLEM= 150; |
| public static final int EXIT_REGISTRY_INVALID_ADDRESS= 151; |
| public static final int EXIT_REGISTRY_CONNECTING_ERROR= 151; |
| public static final int EXIT_REGISTRY_SERVER_STILL_ACTIVE= 152; |
| public static final int EXIT_REGISTRY_ALREADY_BOUND= 153; |
| public static final int EXIT_REGISTRY_CLEAN_FAILED= 155; |
| public static final int EXIT_REGISTRY_BIND_FAILED= 156; |
| public static final int EXIT_START_RENGINE_ERROR= 161; |
| |
| |
| protected final String logPrefix; |
| |
| private final RMIAddress rmiAddress; |
| |
| private final RMIClientSocketFactory rmiCsf; |
| private final RMIServerSocketFactory rmiSsf; |
| |
| private Server mainServer; |
| private boolean isPublished; |
| |
| |
| public RMIServerControl(final String name, final Map<String, String> options) { |
| super(options); |
| final int lastSegment= name.lastIndexOf('/'); |
| this.logPrefix= "[Control:"+((lastSegment >= 0) ? name.substring(lastSegment+1) : name)+"]"; |
| |
| { RMIAddress address= null; |
| Exception error= null; |
| try { |
| address= new RMIAddress(name); |
| } |
| catch (final MalformedURLException e) { |
| error= e; |
| } |
| catch (final UnknownHostException e) { |
| error= e; |
| } |
| if (address == null) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} the server address ''{1}'' is invalid."); |
| record.setParameters(new Object[] { this.logPrefix, name }); |
| record.setThrown(error); |
| LOGGER.log(record); |
| |
| exit(EXIT_REGISTRY_INVALID_ADDRESS); |
| } |
| this.rmiAddress= address; |
| } |
| |
| if (options != null) { |
| if (options.containsKey("verbose")) { |
| initVerbose(); |
| } |
| } |
| |
| this.rmiCsf= RjsComConfig.getRMIServerClientSocketFactory( |
| this.rmiAddress.isSsl() ); |
| this.rmiSsf= RjsComConfig.getRMIServerServerSocketFactory( |
| this.rmiAddress.isSsl() ); |
| } |
| |
| |
| public RMIClientSocketFactory getRmiClientSocketFactory() { |
| return this.rmiCsf; |
| } |
| |
| public RMIServerSocketFactory getRmiServerSocketFactory() { |
| return this.rmiSsf; |
| } |
| |
| |
| public SrvEngineServer initServer() { |
| LOGGER.log(Level.INFO, "{0} Initializing R engine server...", this.logPrefix); |
| try { |
| final String serverType= getOptions().get("server"); //$NON-NLS-1$ |
| if (serverType == null) { |
| final ServerAuthMethod auth= createServerAuth(getOptions().remove("auth")); //$NON-NLS-1$ |
| return new SrvEngineServer(this, auth); |
| } |
| else { |
| final Class<SrvEngineServer> serverClass= (Class<SrvEngineServer>) Class.forName(serverType); |
| final Constructor<SrvEngineServer> constructor= serverClass.getConstructor(RMIServerControl.class); |
| return constructor.newInstance(this); |
| } |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} Failed to initialize R engine server."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| |
| exit(EXIT_INIT_RENGINE_ERROR); |
| throw new RuntimeException(); |
| } |
| } |
| |
| protected RMIRegistry getRmiRegistry() throws RemoteException { |
| return new RMIRegistry(this.rmiAddress.getRegistryAddress(), false); |
| } |
| |
| public String getName() { |
| return this.rmiAddress.getName(); |
| } |
| |
| public Remote exportObject(final Remote obj) throws RemoteException { |
| return UnicastRemoteObject.exportObject(obj, 0, this.rmiCsf, this.rmiSsf); |
| } |
| |
| |
| protected void publishServer(final SrvEngineServer server) { |
| try { |
| { final Policy policy= Policy.getPolicy();// load security file first! |
| System.setSecurityManager(new SecurityManager()); |
| } |
| final RMIRegistry rmiRegistry= getRmiRegistry(); |
| Server stub= (Server) exportObject(server); |
| this.mainServer= server; |
| try { |
| rmiRegistry.getRegistry().bind(getName(), stub); |
| } |
| catch (final AlreadyBoundException boundException) { |
| if (server.getConfigUnbindOnStartup() && unbindDead() == 0) { |
| rmiRegistry.getRegistry().bind(getName(), stub); |
| } |
| else { |
| throw boundException; |
| } |
| } |
| catch (final RemoteException remoteException) { |
| if (!this.rmiAddress.isSsl() |
| && remoteException.getCause() instanceof UnmarshalException |
| && remoteException.getCause().getCause() instanceof StreamCorruptedException |
| && RjsComConfig.getRMIServerClientSocketFactory(false) != null) { |
| stub= null; |
| try { |
| UnicastRemoteObject.unexportObject(server, true); |
| stub= (Server) UnicastRemoteObject.exportObject(server, 0, |
| null, RjsComConfig.getRMIServerServerSocketFactory(false) ); |
| } |
| catch (final Exception testException) {} |
| if (stub != null) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} caught StreamCorruptedException \nretrying without socket factory to reveal other potential problems."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(remoteException); |
| LOGGER.log(record); |
| rmiRegistry.getRegistry().bind(getName(), stub); |
| rmiRegistry.getRegistry().unbind(getName()); |
| throw new RjException("No error without socket factory, use the Java property 'org.eclipse.statet.rj.rmi.disableSocketFactory' to disable the factory."); |
| } |
| } |
| throw remoteException; |
| } |
| Runtime.getRuntime().addShutdownHook(new Thread() { |
| @Override |
| public void run() { |
| checkCleanup(); |
| } |
| }); |
| this.isPublished= true; |
| LOGGER.log(Level.INFO, "{0} server is added to registry - ready.", this.logPrefix); |
| |
| return; |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} init server failed."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| |
| if (e instanceof AlreadyBoundException) { |
| exit(EXIT_REGISTRY_ALREADY_BOUND); |
| } |
| |
| checkCleanup(); |
| exit(EXIT_REGISTRY_BIND_FAILED); |
| } |
| } |
| |
| /** |
| * @return <code>true</code> if it was removed, otherwise <code>false</code> |
| */ |
| protected int unbindDead() { |
| Remote remote; |
| try { |
| final RMIRegistry registry= getRmiRegistry(); |
| remote= registry.getRegistry().lookup(getName()); |
| } |
| catch (final NotBoundException lookupException) { |
| return 0; |
| } |
| catch (final RemoteException lookupException) { |
| return EXIT_REGISTRY_CONNECTING_ERROR; |
| } |
| if (!(remote instanceof Server)) { |
| return 2; |
| } |
| try { |
| ((Server) remote).getInfo(); |
| return EXIT_REGISTRY_SERVER_STILL_ACTIVE; |
| } |
| catch (final RemoteException deadException) { |
| try { |
| final RMIRegistry rmiRegistry= getRmiRegistry(); |
| rmiRegistry.getRegistry().unbind(getName()); |
| LOGGER.log(Level.INFO, |
| "{0} dead server removed from registry.", |
| this.logPrefix); |
| return 0; |
| } |
| catch (final Exception unbindException) { |
| return EXIT_REGISTRY_CLEAN_FAILED; |
| } |
| } |
| } |
| |
| public void checkCleanup() { |
| if (this.mainServer == null) { |
| return; |
| } |
| LOGGER.log(Level.INFO, "{0} cleaning up server resources...", this.logPrefix); |
| try { |
| final RMIRegistry rmiRegistry= getRmiRegistry(); |
| rmiRegistry.getRegistry().unbind(getName()); |
| } |
| catch (final NotBoundException e) { |
| // ok |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(this.isPublished ? Level.SEVERE : Level.INFO, |
| "{0} cleaning up server resources failed."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| } |
| try { |
| UnicastRemoteObject.unexportObject(this.mainServer, true); |
| } |
| catch (final NoSuchObjectException e) { |
| // ok |
| } |
| this.mainServer= null; |
| System.gc(); |
| } |
| |
| public ServerAuthMethod createServerAuth(final String config) throws RjException { |
| // auth |
| final String authType; |
| final String authConfig; |
| try { |
| final ArgKeyValue auth= ServerUtils.getArgSubValue(config); |
| switch (auth.getKey()) { |
| case "": //$NON-NLS-1$ |
| throw new RjInvalidConfigurationException("Missing 'auth' configuration"); |
| case "none": //$NON-NLS-1$ |
| authType= "org.eclipse.statet.rj.server.srvext.auth.NoAuthMethod"; |
| break; |
| case "name-pass": //$NON-NLS-1$ |
| authType= "org.eclipse.statet.rj.server.srvext.auth.SimpleNamePassAuthMethod"; |
| break; |
| case "fx": //$NON-NLS-1$ |
| authType= "org.eclipse.statet.rj.server.srvext.auth.FxAuthMethod"; |
| break; |
| case "local-shaj": //$NON-NLS-1$ |
| authType= "org.eclipse.statet.rj.server.srvext.auth.LocalShajAuthMethod"; |
| break; |
| default: |
| authType= auth.getKey(); |
| break; |
| } |
| authConfig= auth.getValue(); |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} init authentication method failed."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| throw new RjInvalidConfigurationException("Init authentication method failed.", e); |
| } |
| try { |
| final Class<ServerAuthMethod> authClazz= (Class<ServerAuthMethod>) Class.forName(authType); |
| final ServerAuthMethod authMethod= authClazz.newInstance(); |
| authMethod.init(authConfig); |
| return authMethod; |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} init authentication method ''{1}'' failed."); |
| record.setParameters(new Object[] { this.logPrefix, authType }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| throw new RjException(String.format("Init authentication method failed '%1$s'.", authType), |
| e ); |
| } |
| } |
| |
| |
| public void start(final SrvEngineServer server) { |
| try { |
| server.start(new ServerRuntimePlugin() { |
| |
| @Override |
| public String getSymbolicName() { |
| return "rmi"; |
| } |
| |
| @Override |
| public void rjIdle() throws Exception { |
| } |
| |
| @Override |
| public void rjStop(final int state) throws Exception { |
| if (state == 0) { |
| try { |
| Thread.sleep(1000); |
| } |
| catch (final InterruptedException e) { |
| } |
| } |
| checkCleanup(); |
| } |
| |
| }); |
| } |
| catch (final Exception e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} starting R engine server failed."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| |
| exit(EXIT_INIT_RENGINE_ERROR | 8); |
| } |
| |
| publishServer(server); |
| } |
| |
| public void clean() { |
| final int dead= unbindDead(); |
| if (dead == 0) { |
| exit(0); |
| return; |
| } |
| if (dead == EXIT_REGISTRY_SERVER_STILL_ACTIVE && !getOptions().containsKey("force")) { |
| exit(EXIT_REGISTRY_SERVER_STILL_ACTIVE); |
| } |
| try { |
| final RMIRegistry rmiRegistry= getRmiRegistry(); |
| rmiRegistry.getRegistry().unbind(getName()); |
| LOGGER.log(Level.INFO, |
| "{0} server removed from registry.", |
| this.logPrefix); |
| } |
| catch (final NotBoundException e) { |
| exit(0); |
| } |
| catch (final RemoteException e) { |
| final LogRecord record= new LogRecord(Level.SEVERE, |
| "{0} removing server from registry failed."); |
| record.setParameters(new Object[] { this.logPrefix }); |
| record.setThrown(e); |
| LOGGER.log(record); |
| |
| exit(EXIT_REGISTRY_CONNECTING_ERROR); |
| } |
| } |
| |
| } |