/*=============================================================================# # Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of either (per the licensee's choosing) # - the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html, or # - the GNU Lesser General Public License v2.1 or newer # which accompanies this distribution, and is available at # http://www.gnu.org/licenses/lgpl.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.rj.server.srvImpl; import java.io.StreamCorruptedException; 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.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import de.walware.rj.RjException; import de.walware.rj.RjInvalidConfigurationException; import de.walware.rj.server.RjsComConfig; import de.walware.rj.server.Server; import de.walware.rj.server.jri.loader.JRIServerLoader; import de.walware.rj.server.srvext.ServerAuthMethod; import de.walware.rj.server.srvext.ServerRuntimePlugin; import de.walware.rj.server.srvext.ServerUtil; public class AbstractServerControl { public static final int EXIT_ARGS_PROBLEM = 130; public static final int EXIT_ARGS_MISSING = 131; public static final int EXIT_ARGS_INVALID = 132; public static final int EXIT_INIT_PROBLEM = 140; public static final int EXIT_INIT_LOGGING_ERROR = 141; public static final int EXIT_INIT_AUTHMETHOD_ERROR = 143; public static final int EXIT_INIT_RENGINE_ERROR = 145; 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 static final Logger LOGGER = Logger.getLogger("de.walware.rj.server"); protected static Map<String, String> cliGetArgs(final String[] args, final int first) { final Map<String, String> resolved = new HashMap<>(); for (int i = first; i < args.length; i++) { if (args[i].length() == 0 || args[i].charAt(0) != '-') { continue; } final int split = args[i].indexOf('='); if (split > 1) { resolved.put(args[i].substring(1, split), args[i].substring(split+1)); } else if (split < 0) { resolved.put(args[i].substring(1), null); } } if (System.getProperty("de.walware.rj.verbose", "false").equals("true")) { resolved.put("verbose", "true"); } return resolved; } public static void initVerbose() { if (VERBOSE) { return; } VERBOSE = true; VERBOSE_ON_ERROR = false; LOGGER.setLevel(Level.ALL); // default is Level.INFO // System.setProperty("java.rmi.server.logCalls", "true"); // RemoteServer.setLog(System.err); ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); Logger logger = LOGGER; SEARCH_CONSOLE: while (logger != null) { for (final Handler handler : logger.getHandlers()) { if (handler instanceof ConsoleHandler) { handler.setLevel(Level.ALL); break SEARCH_CONSOLE; } } if (!logger.getUseParentHandlers()) { break SEARCH_CONSOLE; } logger = logger.getParent(); } final StringBuilder sb = new StringBuilder(256); LOGGER.log(Level.CONFIG, "verbose mode enabled."); sb.setLength(0); sb.append("java properties:"); ServerUtil.prettyPrint(System.getProperties(), sb); LOGGER.log(Level.CONFIG, sb.toString()); sb.setLength(0); sb.append("env variables:"); ServerUtil.prettyPrint(System.getenv(), sb); LOGGER.log(Level.CONFIG, sb.toString()); } private static boolean VERBOSE; private static boolean VERBOSE_ON_ERROR; public static void exit(final int status) { try { if (status != 0 && VERBOSE_ON_ERROR) { initVerbose(); } System.err.flush(); System.out.flush(); } finally { System.exit(status); } } public Remote exportObject(final Remote obj) throws RemoteException { RMIClientSocketFactory csf = RjsComConfig.getRMIServerClientSocketFactory(); RMIServerSocketFactory ssf = null; if (this.rmiAddress.isSSL()) { csf = new SslRMIClientSocketFactory(); ssf = new SslRMIServerSocketFactory(null, null, true); } return UnicastRemoteObject.exportObject(obj, 0, csf, ssf); } protected final String logPrefix; private final RMIAddress rmiAddress; protected final Map<String, String> args; protected Server mainServer; private boolean isPublished; protected AbstractServerControl(final String name, final Map<String, String> args) { final int lastSegment = name.lastIndexOf('/'); this.logPrefix = "[Control:"+((lastSegment >= 0) ? name.substring(lastSegment+1) : name)+"]"; this.args = args; { 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 (args != null) { if (args.containsKey("verbose")) { initVerbose(); } if (args.containsKey("embedded")) { if (System.getProperty("de.walware.rj.rmi.disableSocketFactory") == null) { System.setProperty("de.walware.rj.rmi.disableSocketFactory", "true"); } if (!VERBOSE) { VERBOSE_ON_ERROR = true; } } } } public boolean initREngine(final DefaultServerImpl server) { InternalEngine engine = null; try { engine = new JRIServerLoader().loadServer(this, this.args, server, 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(); }; }); server.setEngine(engine); return true; } catch (final Throwable e) { final LogRecord record = new LogRecord(Level.SEVERE, "{0} init JRI/Rengine failed."); record.setParameters(new Object[] { this.logPrefix }); record.setThrown(e); LOGGER.log(record); checkCleanup(); return false; } } protected Registry getRegistry() throws RemoteException { RMIClientSocketFactory csf = null; if (this.rmiAddress.isSSL()) { csf = new SslRMIClientSocketFactory(); } return LocateRegistry.getRegistry(this.rmiAddress.getHost(), this.rmiAddress.getPortNum(), csf); } public String getName() { return this.rmiAddress.getName(); } protected void publishServer(final Server server) { try { System.setSecurityManager(new SecurityManager()); final Registry registry = getRegistry(); Server stub = (Server) exportObject(server); this.mainServer = server; try { registry.bind(getName(), stub); } catch (final AlreadyBoundException boundException) { if (unbindDead() == 0) { registry.bind(getName(), stub); } else { throw boundException; } } catch (final RemoteException remoteException) { if (remoteException.getCause() instanceof UnmarshalException && remoteException.getCause().getCause() instanceof StreamCorruptedException && RjsComConfig.getRMIServerClientSocketFactory() != null) { stub = null; try { UnicastRemoteObject.unexportObject(server, true); stub = (Server) UnicastRemoteObject.exportObject(server, 0, null, null); } 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); registry.bind(getName(), stub); registry.unbind(getName()); throw new RjException("No error without socket factory, use the Java property 'de.walware.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() { return 1; } public void checkCleanup() { if (this.mainServer == null) { return; } LOGGER.log(Level.INFO, "{0} cleaning up server resources...", this.logPrefix); try { final Registry registry = getRegistry(); registry.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 String[] auth = ServerUtil.getArgSubValue(config); if (auth[0].length() == 0) { throw new RjInvalidConfigurationException("Missing 'auth' configuration"); } else if (auth[0].equals("none")) { authType = "de.walware.rj.server.srvstdext.NoAuthMethod"; } else if (auth[0].equals("name-pass")) { authType = "de.walware.rj.server.srvstdext.SimpleNamePassAuthMethod"; } else if (auth[0].equals("fx")) { authType = "de.walware.rj.server.srvstdext.FxAuthMethod"; } else if (auth[0].equals("local-shaj")) { authType = "de.walware.rj.server.authShaj.LocalShajAuthMethod"; } else { authType = auth[0]; } authConfig = auth[1]; } 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(MessageFormat.format("Init authentication method failed ''{0}''.", authType), e); } } }