blob: 09254e0d13bd038282f85b62cc2f675f884de002 [file] [log] [blame]
/*=============================================================================#
# 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);
}
}
}