/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.tools; import java.net.URI; import java.sql.Connection; import java.sql.SQLException; import org.h2.constant.ErrorCode; import org.h2.constant.SysProperties; import org.h2.message.DbException; import org.h2.message.TraceSystem; import org.h2.server.Service; import org.h2.server.ShutdownHandler; import org.h2.server.TcpServer; import org.h2.server.pg.PgServer; import org.h2.server.web.WebServer; import org.h2.util.StringUtils; import org.h2.util.Tool; import org.h2.util.Utils; /** * Starts the H2 Console (web-) server, TCP, and PG server. * @h2.resource */ public class Server extends Tool implements Runnable, ShutdownHandler { private Service service; private Server web, tcp, pg; private ShutdownHandler shutdownHandler; private boolean started; public Server() { // nothing to do } /** * Create a new server for the given service. * * @param service the service * @param args the command line arguments */ public Server(Service service, String... args) throws SQLException { verifyArgs(args); this.service = service; try { service.init(args); } catch (Exception e) { throw DbException.toSQLException(e); } } /** * When running without options, -tcp, -web, -browser and -pg are started. * <br /> * Options are case sensitive. Supported options are: * <table> * <tr><td>[-help] or [-?]</td> * <td>Print the list of options</td></tr> * <tr><td>[-web]</td> * <td>Start the web server with the H2 Console</td></tr> * <tr><td>[-webAllowOthers]</td> * <td>Allow other computers to connect - see below</td></tr> * <tr><td>[-webDaemon]</td> * <td>Use a daemon thread</td></tr> * <tr><td>[-webPort <port>]</td> * <td>The port (default: 8082)</td></tr> * <tr><td>[-webSSL]</td> * <td>Use encrypted (HTTPS) connections</td></tr> * <tr><td>[-browser]</td> * <td>Start a browser connecting to the web server</td></tr> * <tr><td>[-tcp]</td> * <td>Start the TCP server</td></tr> * <tr><td>[-tcpAllowOthers]</td> * <td>Allow other computers to connect - see below</td></tr> * <tr><td>[-tcpDaemon]</td> * <td>Use a daemon thread</td></tr> * <tr><td>[-tcpPort <port>]</td> * <td>The port (default: 9092)</td></tr> * <tr><td>[-tcpSSL]</td> * <td>Use encrypted (SSL) connections</td></tr> * <tr><td>[-tcpPassword <pwd>]</td> * <td>The password for shutting down a TCP server</td></tr> * <tr><td>[-tcpShutdown "<url>"]</td> * <td>Stop the TCP server; example: tcp://localhost</td></tr> * <tr><td>[-tcpShutdownForce]</td> * <td>Do not wait until all connections are closed</td></tr> * <tr><td>[-pg]</td> * <td>Start the PG server</td></tr> * <tr><td>[-pgAllowOthers]</td> * <td>Allow other computers to connect - see below</td></tr> * <tr><td>[-pgDaemon]</td> * <td>Use a daemon thread</td></tr> * <tr><td>[-pgPort <port>]</td> * <td>The port (default: 5435)</td></tr> * <tr><td>[-properties "<dir>"]</td> * <td>Server properties (default: ~, disable: null)</td></tr> * <tr><td>[-baseDir <dir>]</td> * <td>The base directory for H2 databases (all servers)</td></tr> * <tr><td>[-ifExists]</td> * <td>Only existing databases may be opened (all servers)</td></tr> * <tr><td>[-trace]</td> * <td>Print additional trace information (all servers)</td></tr> * </table> * The options -xAllowOthers are potentially risky. * <br /> * For details, see Advanced Topics / Protection against Remote Access. * @h2.resource * * @param args the command line arguments */ public static void main(String... args) throws SQLException { new Server().runTool(args); } private void verifyArgs(String... args) throws SQLException { for (int i = 0; args != null && i < args.length; i++) { String arg = args[i]; if (arg == null) { continue; } else if ("-?".equals(arg) || "-help".equals(arg)) { // ok } else if (arg.startsWith("-web")) { if ("-web".equals(arg)) { // ok } else if ("-webAllowOthers".equals(arg)) { // no parameters } else if ("-webDaemon".equals(arg)) { // no parameters } else if ("-webSSL".equals(arg)) { // no parameters } else if ("-webPort".equals(arg)) { i++; } else { throwUnsupportedOption(arg); } } else if ("-browser".equals(arg)) { // ok } else if (arg.startsWith("-tcp")) { if ("-tcp".equals(arg)) { // ok } else if ("-tcpAllowOthers".equals(arg)) { // no parameters } else if ("-tcpDaemon".equals(arg)) { // no parameters } else if ("-tcpSSL".equals(arg)) { // no parameters } else if ("-tcpPort".equals(arg)) { i++; } else if ("-tcpPassword".equals(arg)) { i++; } else if ("-tcpShutdown".equals(arg)) { i++; } else if ("-tcpShutdownForce".equals(arg)) { // ok } else { throwUnsupportedOption(arg); } } else if (arg.startsWith("-pg")) { if ("-pg".equals(arg)) { // ok } else if ("-pgAllowOthers".equals(arg)) { // no parameters } else if ("-pgDaemon".equals(arg)) { // no parameters } else if ("-pgPort".equals(arg)) { i++; } else { throwUnsupportedOption(arg); } } else if (arg.startsWith("-ftp")) { if ("-ftpPort".equals(arg)) { i++; } else if ("-ftpDir".equals(arg)) { i++; } else if ("-ftpRead".equals(arg)) { i++; } else if ("-ftpWrite".equals(arg)) { i++; } else if ("-ftpWritePassword".equals(arg)) { i++; } else if ("-ftpTask".equals(arg)) { // no parameters } else { throwUnsupportedOption(arg); } } else if ("-properties".equals(arg)) { i++; } else if ("-trace".equals(arg)) { // no parameters } else if ("-ifExists".equals(arg)) { // no parameters } else if ("-baseDir".equals(arg)) { i++; } else if ("-key".equals(arg)) { i += 2; } else if ("-tool".equals(arg)) { // no parameters } else { throwUnsupportedOption(arg); } } } public void runTool(String... args) throws SQLException { boolean tcpStart = false, pgStart = false, webStart = false; boolean browserStart = false; boolean tcpShutdown = false, tcpShutdownForce = false; String tcpPassword = ""; String tcpShutdownServer = ""; boolean startDefaultServers = true; for (int i = 0; args != null && i < args.length; i++) { String arg = args[i]; if (arg == null) { continue; } else if ("-?".equals(arg) || "-help".equals(arg)) { showUsage(); return; } else if (arg.startsWith("-web")) { if ("-web".equals(arg)) { startDefaultServers = false; webStart = true; } else if ("-webAllowOthers".equals(arg)) { // no parameters } else if ("-webDaemon".equals(arg)) { // no parameters } else if ("-webSSL".equals(arg)) { // no parameters } else if ("-webPort".equals(arg)) { i++; } else { showUsageAndThrowUnsupportedOption(arg); } } else if ("-browser".equals(arg)) { startDefaultServers = false; browserStart = true; } else if (arg.startsWith("-tcp")) { if ("-tcp".equals(arg)) { startDefaultServers = false; tcpStart = true; } else if ("-tcpAllowOthers".equals(arg)) { // no parameters } else if ("-tcpDaemon".equals(arg)) { // no parameters } else if ("-tcpSSL".equals(arg)) { // no parameters } else if ("-tcpPort".equals(arg)) { i++; } else if ("-tcpPassword".equals(arg)) { tcpPassword = args[++i]; } else if ("-tcpShutdown".equals(arg)) { startDefaultServers = false; tcpShutdown = true; tcpShutdownServer = args[++i]; } else if ("-tcpShutdownForce".equals(arg)) { tcpShutdownForce = true; } else { showUsageAndThrowUnsupportedOption(arg); } } else if (arg.startsWith("-pg")) { if ("-pg".equals(arg)) { startDefaultServers = false; pgStart = true; } else if ("-pgAllowOthers".equals(arg)) { // no parameters } else if ("-pgDaemon".equals(arg)) { // no parameters } else if ("-pgPort".equals(arg)) { i++; } else { showUsageAndThrowUnsupportedOption(arg); } } else if ("-properties".equals(arg)) { i++; } else if ("-trace".equals(arg)) { // no parameters } else if ("-ifExists".equals(arg)) { // no parameters } else if ("-baseDir".equals(arg)) { i++; } else if ("-key".equals(arg)) { i += 2; } else { showUsageAndThrowUnsupportedOption(arg); } } verifyArgs(args); if (startDefaultServers) { tcpStart = true; pgStart = true; webStart = true; browserStart = true; } // TODO server: maybe use one single properties file? if (tcpShutdown) { out.println("Shutting down TCP Server at " + tcpShutdownServer); shutdownTcpServer(tcpShutdownServer, tcpPassword, tcpShutdownForce, false); } try { if (webStart) { web = createWebServer(args); web.setShutdownHandler(this); SQLException result = null; try { web.start(); } catch (Exception e) { result = DbException.toSQLException(e); } out.println(web.getStatus()); // start browser in any case (even if the server is already running) // because some people don't look at the output, // but are wondering why nothing happens if (browserStart) { try { openBrowser(web.getURL()); } catch (Exception e) { out.println(e.getMessage()); } } if (result != null) { throw result; } } if (tcpStart) { tcp = createTcpServer(args); tcp.start(); out.println(tcp.getStatus()); tcp.setShutdownHandler(this); } if (pgStart) { pg = createPgServer(args); pg.start(); out.println(pg.getStatus()); } } catch (SQLException e) { stopAll(); throw e; } } /** * Shutdown one or all TCP server. If force is set to false, the server will not * allow new connections, but not kill existing connections, instead it will * stop if the last connection is closed. If force is set to true, existing * connections are killed. After calling the method with force=false, it is * not possible to call it again with force=true because new connections are * not allowed. Example: * * <pre> * Server.shutdownTcpServer( * "tcp://localhost:9094", password, true, false); * </pre> * * @param url example: tcp://localhost:9094 * @param password the password to use ("" for no password) * @param force the shutdown (don't wait) * @param all whether all TCP servers that are running in the JVM * should be stopped */ public static void shutdownTcpServer(String url, String password, boolean force, boolean all) throws SQLException { TcpServer.shutdown(url, password, force, all); } /** * Get the status of this server. * * @return the status */ public String getStatus() { StringBuilder buff = new StringBuilder(); if (!started) { buff.append("Not started"); } else if (isRunning(false)) { buff.append(service.getType()). append(" server running at "). append(service.getURL()). append(" ("); if (service.getAllowOthers()) { buff.append("others can connect"); } else { buff.append("only local connections"); } buff.append(')'); } else { buff.append("The "). append(service.getType()). append(" server could not be started. Possible cause: another server is already running at "). append(service.getURL()); } return buff.toString(); } /** * Create a new web server, but does not start it yet. Example: * * <pre> * Server server = Server.createWebServer( * new String[] { "-trace" }).start(); * </pre> * * @param args the argument list * @return the server */ public static Server createWebServer(String... args) throws SQLException { WebServer service = new WebServer(); Server server = new Server(service, args); service.setShutdownHandler(server); return server; } /** * Create a new TCP server, but does not start it yet. Example: * * <pre> * Server server = Server.createTcpServer( * new String[] { "-tcpPort", "9123", "-tcpAllowOthers" }).start(); * </pre> * * @param args the argument list * @return the server */ public static Server createTcpServer(String... args) throws SQLException { TcpServer service = new TcpServer(); Server server = new Server(service, args); service.setShutdownHandler(server); return server; } /** * Create a new PG server, but does not start it yet. * Example: * <pre> * Server server = * Server.createPgServer("-pgAllowOthers").start(); * </pre> * * @param args the argument list * @return the server */ public static Server createPgServer(String... args) throws SQLException { return new Server(new PgServer(), args); } /** * Tries to start the server. * @return the server if successful * @throws SQLException if the server could not be started */ public Server start() throws SQLException { try { started = true; service.start(); String name = service.getName() + " (" + service.getURL() + ")"; Thread t = new Thread(this, name); t.setDaemon(service.isDaemon()); t.start(); for (int i = 1; i < 64; i += i) { wait(i); if (isRunning(false)) { return this; } } if (isRunning(true)) { return this; } throw DbException.get(ErrorCode.EXCEPTION_OPENING_PORT_2, name, "timeout; " + "please check your network configuration, specially the file /etc/hosts"); } catch (DbException e) { throw DbException.toSQLException(e); } } private static void wait(int i) { try { // sleep at most 4096 ms long sleep = (long) i * (long) i; Thread.sleep(sleep); } catch (InterruptedException e) { // ignore } } private void stopAll() { if (web != null && web.isRunning(false)) { web.stop(); web = null; } if (tcp != null && tcp.isRunning(false)) { tcp.stop(); tcp = null; } if (pg != null && pg.isRunning(false)) { pg.stop(); pg = null; } } /** * Checks if the server is running. * * @param traceError if errors should be written * @return if the server is running */ public boolean isRunning(boolean traceError) { return service.isRunning(traceError); } /** * Stops the server. */ public void stop() { started = false; if (service != null) { service.stop(); } } /** * Gets the URL of this server. * * @return the url */ public String getURL() { return service.getURL(); } /** * Gets the port this server is listening on. * * @return the port */ public int getPort() { return service.getPort(); } /** * INTERNAL */ public void run() { try { service.listen(); } catch (Exception e) { TraceSystem.traceThrowable(e); } } /** * INTERNAL */ public void setShutdownHandler(ShutdownHandler shutdownHandler) { this.shutdownHandler = shutdownHandler; } /** * INTERNAL */ public void shutdown() { if (shutdownHandler != null) { shutdownHandler.shutdown(); } else { stopAll(); } } /** * Get the service attached to this server. * * @return the service */ public Service getService() { return service; } /** * Open a new browser tab or window with the given URL. * * @param url the URL to open */ public static void openBrowser(String url) throws Exception { try { String osName = StringUtils.toLowerEnglish(Utils.getProperty("os.name", "linux")); Runtime rt = Runtime.getRuntime(); String browser = Utils.getProperty(SysProperties.H2_BROWSER, null); if (browser != null) { if (browser.startsWith("call:")) { browser = browser.substring("call:".length()); Utils.callStaticMethod(browser, url); } else if (browser.indexOf("%url") >= 0) { String[] args = StringUtils.arraySplit(browser, ',', false); for (int i = 0; i < args.length; i++) { args[i] = StringUtils.replaceAll(args[i], "%url", url); } rt.exec(args); } else if (osName.indexOf("windows") >= 0) { rt.exec(new String[] { "cmd.exe", "/C", browser, url }); } else { rt.exec(new String[] { browser, url }); } return; } try { Class<?> desktopClass = Class.forName("java.awt.Desktop"); // Desktop.isDesktopSupported() Boolean supported = (Boolean) desktopClass. getMethod("isDesktopSupported"). invoke(null, new Object[0]); URI uri = new URI(url); if (supported) { // Desktop.getDesktop(); Object desktop = desktopClass.getMethod("getDesktop"). invoke(null, new Object[0]); // desktop.browse(uri); desktopClass.getMethod("browse", URI.class). invoke(desktop, uri); return; } } catch (Exception e) { // ignore } if (osName.indexOf("windows") >= 0) { rt.exec(new String[] { "rundll32", "url.dll,FileProtocolHandler", url }); } else if (osName.indexOf("mac") >= 0 || osName.indexOf("darwin") >= 0) { // Mac OS: to open a page with Safari, use "open -a Safari" Runtime.getRuntime().exec(new String[] { "open", url }); } else { String[] browsers = { "google-chrome", "firefox", "mozilla-firefox", "mozilla", "konqueror", "netscape", "opera", "midori" }; boolean ok = false; for (String b : browsers) { try { rt.exec(new String[] { b, url }); ok = true; break; } catch (Exception e) { // ignore and try the next } } if (!ok) { // No success in detection. throw new Exception("Browser detection failed and system property " + SysProperties.H2_BROWSER + " not set"); } } } catch (Exception e) { throw new Exception("Failed to start a browser to open the URL " + url + ": " + e.getMessage()); } } /** * Start a web server and a browser that uses the given connection. The * current transaction is preserved. This is specially useful to manually * inspect the database when debugging. This method return as soon as the * user has disconnected. * * @param conn the database connection (the database must be open) */ public static void startWebServer(Connection conn) throws SQLException { WebServer webServer = new WebServer(); Server web = new Server(webServer, new String[] { "-webPort", "0" }); web.start(); Server server = new Server(); server.web = web; webServer.setShutdownHandler(server); String url = webServer.addSession(conn); try { Server.openBrowser(url); while (!webServer.isStopped()) { Thread.sleep(1000); } } catch (Exception e) { // ignore } } }