0001: /* Copyright (c) 2001-2005, The HSQL Development Group
0002: * All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * Redistributions of source code must retain the above copyright notice, this
0008: * list of conditions and the following disclaimer.
0009: *
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * Neither the name of the HSQL Development Group nor the names of its
0015: * contributors may be used to endorse or promote products derived from this
0016: * software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
0022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package org.hsqldb;
0032:
0033: import java.io.File;
0034: import java.io.IOException;
0035: import java.io.PrintWriter;
0036: import java.net.ServerSocket;
0037: import java.net.Socket;
0038: import java.net.UnknownHostException;
0039: import java.util.Enumeration;
0040: import java.util.StringTokenizer;
0041:
0042: import org.hsqldb.lib.ArrayUtil;
0043: import org.hsqldb.lib.FileUtil;
0044: import org.hsqldb.lib.HashSet;
0045: import org.hsqldb.lib.Iterator;
0046: import org.hsqldb.lib.StopWatch;
0047: import org.hsqldb.lib.StringUtil;
0048: import org.hsqldb.lib.WrapperIterator;
0049: import org.hsqldb.lib.java.JavaSystem;
0050: import org.hsqldb.persist.HsqlDatabaseProperties;
0051: import org.hsqldb.persist.HsqlProperties;
0052: import org.hsqldb.resources.BundleHandler;
0053:
0054: // fredt@users 20020215 - patch 1.7.0
0055: // methods reorganised to use new HsqlProperties class
0056: // fredt@users 20020424 - patch 1.7.0 - shutdown without exit
0057: // see the comments in ServerConnection.java
0058: // unsaved@users 20021113 - patch 1.7.2 - SSL support
0059: // boucherb@users 20030510-14 - 1.7.2 - SSL support moved to factory interface
0060: // boucherb@users 20030510-14 - 1.7.2 - service control, JavaBean API
0061: // fredt@users 20030916 - 1.7.2 - review, simplification and multiple DB's
0062: // fredt@users 20040320 - 1.7.2 - review and correction
0063: // fredt@users 20050225 - 1.8.0 - minor corrections
0064: // fredt@users 20051231 - 1.8.1 - support for remote opening of databases
0065:
0066: /**
0067: * The HSQLDB HSQL protocol network database server. <p>
0068: *
0069: * A Server object acts as a network database server and is one way of using
0070: * the client-server mode of HSQLDB Database Engine. Instances of this
0071: * class handle native HSQL protocol connections exclusively, allowing database
0072: * queries to be performed efficienly across the network. Server's direct
0073: * descendent, WebServer, handles HTTP protocol connections exclusively,
0074: * allowing HSQL protocol to be tunneled over HTTP to avoid sandbox and
0075: * firewall issues, albeit less efficiently. <p>
0076: *
0077: * There are a number of ways to configure and start a Server instance. <p>
0078: *
0079: * When started from the command line or programatically via the main(String[])
0080: * method, configuration occurs in three phases, with later phases overriding
0081: * properties set by previous phases:
0082: *
0083: * <ol>
0084: * <li>Upon construction, a Server object is assigned a set of default
0085: * properties. <p>
0086: *
0087: * <li>If it exists, properties are loaded from a file named
0088: * 'server.properties' in the present working directory. <p>
0089: *
0090: * <li>The command line arguments (alternatively, the String[] passed to
0091: * main()) are parsed and used to further configure the Server's
0092: * properties. <p>
0093: *
0094: * </ol> <p>
0095: *
0096: * From the command line, the options are as follows: <p>
0097: * <pre>
0098: * +----------------+-------------+----------+------------------------------+
0099: * | OPTION | TYPE | DEFAULT | DESCRIPTION |
0100: * +----------------+-------------+----------+------------------------------|
0101: * | -? | -- | -- | prints this message |
0102: * | -address | name|number | any | server inet address |
0103: * | -port | number | 9001/544 | port at which server listens |
0104: * | -database.i | [type]spec | 0=test | path of database i |
0105: * | -dbname.i | alias | -- | url alias for database i |
0106: * | -silent | true|false | true | false => display all queries |
0107: * | -trace | true|false | false | display JDBC trace messages |
0108: * | -tls | true|false | false | TLS/SSL (secure) sockets |
0109: * | -no_system_exit| true|false | false | do not issue System.exit() |
0110: * | -remote_open | true|false | false | can open databases remotely |
0111: * +----------------+-------------+----------+------------------------------+
0112: * </pre>
0113: *
0114: * The <em>database.i</em> and <em>dbname.i</em> options need further
0115: * explanation:
0116: *
0117: * <ul>
0118: * <li>Multiple databases can be served by each instance of the Server.
0119: * The value of <em>i</em> is currently limited to the range 0..9,
0120: * allowing up to 10 different databases. Any number is this range
0121: * can be used.<p>
0122: *
0123: * <li>The value assigned to <em>database.i</em> is interpreted using the
0124: * format <b>'[type]spec'</b>, where the optional <em>type</em> component
0125: * is one of <b>'file:'</b>, <b>'res:'</b> or <b>'mem:'</b> and the
0126: * <em>spec</em> component is interpreted in the context of the
0127: * <em>type</em> component. <p>
0128: *
0129: * If omitted, the <em>type</em> component is taken to be
0130: * <b>'file:'</b>. <p>
0131: *
0132: * A full description of how
0133: * <b>'[type]spec'</b> values are interpreted appears in the overview for
0134: * {@link org.hsqldb.jdbc.jdbcConnection jdbcConnection}. <p>
0135: *
0136: * <li>The value assigned to <em>dbname.i</em> is taken to be the key used to
0137: * look up the desired database instance and thus corresponds to the
0138: * <b><alias></b> component of the HSQLDB HSQL protocol database
0139: * connection url:
0140: * 'jdbc:hsqldb:hsql[s]://host[port][/<b><alias></b>]'. <p>
0141: *
0142: * <li>The value of <em>database.0</em> is special. If <em>dbname.0</em>
0143: * is not specified, then this defaults to an empty string and
0144: * a connection is made to <em>database.0</em> path when
0145: * the <b><alias></b> component of an HSQLDB HSQL protocol database
0146: * connection url is omitted. If a <em>database</em> key/value pair is
0147: * found in the properties when the main method is called, this
0148: * pair is supersedes the <em>database.0</em> setting<p>
0149: *
0150: * This behaviour allows the previous
0151: * database connection url format to work with essentially unchanged
0152: * semantics.<p>
0153: *
0154: * <li>When the <em>remote_open</em> property is true, a connection attempt
0155: * to an unopened database results in the database being opened. The URL
0156: * for connection should include the property filepath to specify the path.
0157: * 'jdbc:hsqldb:hsql[s]://host[port]/<b><alias>;filepath=hsqldb:file:<database path></b>'.
0158: * the given alias and filepath value will be associated together. The
0159: * database user and password to start this connection must be valid.
0160: * If this form of connection is used again, after the database has been
0161: * opened, the filepath property is ignored.<p>
0162: *
0163: * <li>Once an alias such as "mydb" has been associated with a path, it cannot
0164: * be reassigned to a different path.<p>
0165: *
0166: * <li>If a database is closed with the SHUTDOWN command, its
0167: * alias is removed. It is then possible to connect to this database again
0168: * with a different (or the same) alias.<p>
0169: *
0170: * <li>If the same database is connected to via two different
0171: * aliases, and then one of the is closed with the SHUTDOWN command, the
0172: * other is also closed.<p>
0173: * </ul>
0174: *
0175: * From the 'server.properties' file, options can be set similarly, using a
0176: * slightly different format. <p>
0177: *
0178: * Here is an example 'server.properties' file:
0179: *
0180: * <pre>
0181: * server.port=9001
0182: * server.database.0=test
0183: * server.dbname.0=...
0184: * ...
0185: * server.database.n=...
0186: * server.dbname.n=...
0187: * server.silent=true
0188: * </pre>
0189: *
0190: * Starting with 1.7.2, Server has been refactored to become a simple JavaBean
0191: * with non-blocking start() and stop() service methods. It is possible to
0192: * configure a Server instance through the JavaBean API as well, but this
0193: * part of the public interface is still under review and will not be finalized
0194: * or documented fully until the final 1.7.2 release. <p>
0195: *
0196: * <b>Note:</b> <p>
0197: *
0198: * The 'no_system_exit' property is of particular interest. <p>
0199: *
0200: * If a Server instance is to run embedded in, say, an application server,
0201: * such as when the jdbcDataSource or HsqlServerFactory classes are used, it
0202: * is typically necessary to avoid calling System.exit() when the Server
0203: * instance shuts down. <p>
0204: *
0205: * By default, 'no_system_exit' is set: <p>
0206: *
0207: * <ol>
0208: * <li><b>true</b> when a Server is started directly from the start()
0209: * method. <p>
0210: *
0211: * <li><b>false</b> when a Server is started from the main(String[])
0212: * method.
0213: * </ol> <p>
0214: *
0215: * These values are natural to their context because the first case allows
0216: * the JVM to exit by default on Server shutdown when a Server instance is
0217: * started from a command line environment, whereas the second case prevents
0218: * a typically unwanted JVM exit on Server shutdown when a Server intance
0219: * is started as part of a larger framework. <p>
0220: *
0221: * Replaces original Hypersonic source of the same name.
0222: *
0223: * @author fredt@users
0224: * @version 1.8.0
0225: * @since 1.7.2
0226: *
0227: * @jmx.mbean
0228: * description="HSQLDB Server"
0229: * extends="org.hsqldb.mx.mbean.RegistrationSupportBaseMBean"
0230: *
0231: * @jboss.xmbean
0232: */
0233: public class Server implements HsqlSocketRequestHandler {
0234:
0235: //
0236: protected static final int serverBundleHandle = BundleHandler
0237: .getBundleHandle("org_hsqldb_Server_messages", null);
0238:
0239: //
0240: HsqlProperties serverProperties;
0241:
0242: //
0243: HashSet serverConnSet;
0244:
0245: //
0246: private String[] dbAlias;
0247: private String[] dbType;
0248: private String[] dbPath;
0249: private HsqlProperties[] dbProps;
0250: private int[] dbID;
0251:
0252: // Currently unused
0253: private int maxConnections;
0254:
0255: //
0256: protected String serverId;
0257: protected int serverProtocol;
0258: protected ThreadGroup serverConnectionThreadGroup;
0259: protected HsqlSocketFactory socketFactory;
0260: protected ServerSocket socket;
0261:
0262: //
0263: private Thread serverThread;
0264: private Throwable serverError;
0265: private volatile int serverState;
0266: private volatile boolean isSilent;
0267: private volatile boolean isRemoteOpen;
0268: private PrintWriter logWriter;
0269: private PrintWriter errWriter;
0270:
0271: //
0272:
0273: /**
0274: * A specialized Thread inner class in which the run() method of this
0275: * server executes.
0276: */
0277: private class ServerThread extends Thread {
0278:
0279: /**
0280: * Constructs a new thread in which to execute the run method
0281: * of this server.
0282: *
0283: * @param name The thread name
0284: */
0285: ServerThread(String name) {
0286:
0287: super (name);
0288:
0289: setName(name + '@'
0290: + Integer.toString(Server.this .hashCode(), 16));
0291: }
0292:
0293: /**
0294: * Executes the run() method of this server
0295: */
0296: public void run() {
0297: Server.this .run();
0298: printWithThread("ServerThread.run() exited");
0299: }
0300: }
0301:
0302: /**
0303: * Creates a new Server instance handling HSQL protocol connections.
0304: */
0305: public Server() {
0306: this (ServerConstants.SC_PROTOCOL_HSQL);
0307: }
0308:
0309: /**
0310: * Creates a new Server instance handling the specified connection
0311: * protocol. <p>
0312: *
0313: * For example, the no-args WebServer constructor invokes this constructor
0314: * with ServerConstants.SC_PROTOCOL_HTTP, while the Server() no args
0315: * contructor invokes this constructor with
0316: * ServerConstants.SC_PROTOCOL_HSQL. <p>
0317: *
0318: * @param protocol the ServerConstants code indicating which
0319: * connection protocol to handle
0320: */
0321: protected Server(int protocol) {
0322: init(protocol);
0323: }
0324:
0325: /**
0326: * Creates and starts a new Server. <p>
0327: *
0328: * Allows starting a Server via the command line interface. <p>
0329: *
0330: * @param args the command line arguments for the Server instance
0331: */
0332: public static void main(String[] args) {
0333:
0334: String propsPath = FileUtil.canonicalOrAbsolutePath("server");
0335: HsqlProperties fileProps = ServerConfiguration
0336: .getPropertiesFromFile(propsPath);
0337: HsqlProperties props = fileProps == null ? new HsqlProperties()
0338: : fileProps;
0339: HsqlProperties stringProps = null;
0340: try {
0341: stringProps = HsqlProperties.argArrayToProps(args,
0342: ServerConstants.SC_KEY_PREFIX);
0343: } catch (ArrayIndexOutOfBoundsException aioob) {
0344: // I'd like to exit with 0 here, but it's possible that user
0345: // has called main() programmatically and does not want us to
0346: // exit.
0347: printHelp("server.help");
0348: return;
0349: }
0350:
0351: if (stringProps != null) {
0352: if (stringProps.getErrorKeys().length != 0) {
0353: printHelp("server.help");
0354:
0355: return;
0356: }
0357:
0358: props.addProperties(stringProps);
0359: }
0360:
0361: ServerConfiguration.translateDefaultDatabaseProperty(props);
0362:
0363: // Standard behaviour when started from the command line
0364: // is to halt the VM when the server shuts down. This may, of
0365: // course, be overridden by whatever, if any, security policy
0366: // is in place.
0367: ServerConfiguration.translateDefaultNoSystemExitProperty(props);
0368:
0369: // finished setting up properties;
0370: Server server = new Server();
0371:
0372: try {
0373: server.setProperties(props);
0374: } catch (Exception e) {
0375: server.printError("Failed to set properties");
0376: server.printStackTrace(e);
0377:
0378: return;
0379: }
0380:
0381: // now messages go to the channel specified in properties
0382: server.print("Startup sequence initiated from main() method");
0383:
0384: if (fileProps != null) {
0385: server.print("Loaded properties from [" + propsPath
0386: + ".properties]");
0387: } else {
0388: server.print("Could not load properties from file");
0389: server.print("Using cli/default properties only");
0390: }
0391:
0392: server.start();
0393: }
0394:
0395: /**
0396: * Checks if this Server object is or is not running and throws if the
0397: * current state does not match the specified value.
0398: *
0399: * @param running if true, ensure the server is running, else ensure the
0400: * server is not running
0401: * @throws RuntimeException if the supplied value does not match the
0402: * current running status
0403: */
0404: public void checkRunning(boolean running) throws RuntimeException {
0405:
0406: int state;
0407: boolean error;
0408:
0409: printWithThread("checkRunning(" + running + ") entered");
0410:
0411: state = getState();
0412: error = (running && state != ServerConstants.SERVER_STATE_ONLINE)
0413: || (!running && state != ServerConstants.SERVER_STATE_SHUTDOWN);
0414:
0415: if (error) {
0416: String msg = "server is " + (running ? "not " : "")
0417: + "running";
0418:
0419: throw new RuntimeException(msg);
0420: }
0421:
0422: printWithThread("checkRunning(" + running + ") exited");
0423: }
0424:
0425: /**
0426: * Closes all connections to this Server.
0427: *
0428: * @jmx.managed-operation
0429: * impact="ACTION"
0430: * description="Closes all open connections"
0431: */
0432: public synchronized void signalCloseAllServerConnections() {
0433:
0434: Iterator it;
0435:
0436: printWithThread("signalCloseAllServerConnections() entered");
0437:
0438: synchronized (serverConnSet) {
0439:
0440: // snapshot
0441: it = new WrapperIterator(serverConnSet.toArray(null));
0442: }
0443:
0444: for (; it.hasNext();) {
0445: ServerConnection sc = (ServerConnection) it.next();
0446:
0447: printWithThread("Closing " + sc);
0448:
0449: // also removes all but one connection from serverConnSet
0450: sc.signalClose();
0451: }
0452:
0453: printWithThread("signalCloseAllServerConnections() exited");
0454: }
0455:
0456: protected void finalize() throws Throwable {
0457:
0458: if (serverThread != null) {
0459: releaseServerSocket();
0460: }
0461: }
0462:
0463: /**
0464: * Retrieves, in string form, this server's host address.
0465: *
0466: * @return this server's host address
0467: *
0468: * @jmx.managed-attribute
0469: * access="read-write"
0470: * description="Host InetAddress"
0471: */
0472: public String getAddress() {
0473:
0474: return socket == null ? serverProperties
0475: .getProperty(ServerConstants.SC_KEY_ADDRESS) : socket
0476: .getInetAddress().getHostAddress();
0477: }
0478:
0479: /**
0480: * Retrieves the url alias (network name) of the i'th database
0481: * that this Server hosts.
0482: *
0483: * @param index the index of the url alias upon which to report
0484: * @param asconfigured if true, report the configured value, else
0485: * the live value
0486: * @return the url alias component of the i'th database
0487: * that this Server hosts, or null if no such name exists.
0488: *
0489: * @jmx.managed-operation
0490: * impact="INFO"
0491: * description="url alias component of the i'th hosted Database"
0492: *
0493: * @jmx.managed-operation-parameter
0494: * name="index"
0495: * type="int"
0496: * position="0"
0497: * description="This Server's index for the hosted Database"
0498: *
0499: * @jmx.managed-operation-parameter
0500: * name="asconfigured"
0501: * type="boolean"
0502: * position="1"
0503: * description="if true, the configured value, else the live value"
0504: */
0505: public String getDatabaseName(int index, boolean asconfigured) {
0506:
0507: if (asconfigured) {
0508: return serverProperties
0509: .getProperty(ServerConstants.SC_KEY_DBNAME + "."
0510: + index);
0511: } else if (getState() == ServerConstants.SERVER_STATE_ONLINE) {
0512: return (dbAlias == null || index < 0 || index >= dbAlias.length) ? null
0513: : dbAlias[index];
0514: } else {
0515: return null;
0516: }
0517: }
0518:
0519: /**
0520: * Retrieves the HSQLDB path descriptor (uri) of the i'th
0521: * Database that this Server hosts.
0522: *
0523: * @param index the index of the uri upon which to report
0524: * @param asconfigured if true, report the configured value, else
0525: * the live value
0526: * @return the HSQLDB database path descriptor of the i'th database
0527: * that this Server hosts, or null if no such path descriptor
0528: * exists
0529: *
0530: * @jmx.managed-operation
0531: * impact="INFO"
0532: * description="For i'th hosted database"
0533: *
0534: * @jmx.managed-operation-parameter
0535: * name="index"
0536: * type="int"
0537: * position="0"
0538: * description="This Server's index for the hosted Database"
0539: *
0540: * @jmx.managed-operation-parameter
0541: * name="asconfigured"
0542: * type="boolean"
0543: * position="1"
0544: * description="if true, the configured value, else the live value"
0545: */
0546: public String getDatabasePath(int index, boolean asconfigured) {
0547:
0548: if (asconfigured) {
0549: return serverProperties
0550: .getProperty(ServerConstants.SC_KEY_DATABASE + "."
0551: + index);
0552: } else if (getState() == ServerConstants.SERVER_STATE_ONLINE) {
0553: return (dbPath == null || index < 0 || index >= dbPath.length) ? null
0554: : dbPath[index];
0555: } else {
0556: return null;
0557: }
0558: }
0559:
0560: public String getDatabaseType(int index) {
0561: return (dbType == null || index < 0 || index >= dbType.length) ? null
0562: : dbType[index];
0563: }
0564:
0565: /**
0566: * Retrieves the name of the web page served when no page is specified.
0567: * This attribute is relevant only when server protocol is HTTP(S).
0568: *
0569: * @return the name of the web page served when no page is specified
0570: *
0571: * @jmx.managed-attribute
0572: * access="read-write"
0573: * description="Used when server protocol is HTTP(S)"
0574: */
0575: public String getDefaultWebPage() {
0576: return "[IGNORED]";
0577: }
0578:
0579: /**
0580: * Retrieves a String object describing the command line and
0581: * properties options for this Server.
0582: *
0583: * @return the command line and properties options help for this Server
0584: */
0585: public String getHelpString() {
0586: return BundleHandler.getString(serverBundleHandle,
0587: "server.help");
0588: }
0589:
0590: /**
0591: * Retrieves the PrintWriter to which server errors are printed.
0592: *
0593: * @return the PrintWriter to which server errors are printed.
0594: */
0595: public PrintWriter getErrWriter() {
0596: return errWriter;
0597: }
0598:
0599: /**
0600: * Retrieves the PrintWriter to which server messages are printed.
0601: *
0602: * @return the PrintWriter to which server messages are printed.
0603: */
0604: public PrintWriter getLogWriter() {
0605: return logWriter;
0606: }
0607:
0608: /**
0609: * Retrieves this server's host port.
0610: *
0611: * @return this server's host port
0612: *
0613: * @jmx.managed-attribute
0614: * access="read-write"
0615: * description="At which ServerSocket listens for connections"
0616: */
0617: public int getPort() {
0618:
0619: return serverProperties.getIntegerProperty(
0620: ServerConstants.SC_KEY_PORT, ServerConfiguration
0621: .getDefaultPort(serverProtocol, isTls()));
0622: }
0623:
0624: /**
0625: * Retrieves this server's product name. <p>
0626: *
0627: * Typically, this will be something like: "HSQLDB xxx server".
0628: *
0629: * @return the product name of this server
0630: *
0631: * @jmx.managed-attribute
0632: * access="read-only"
0633: * description="Of Server"
0634: */
0635: public String getProductName() {
0636: return "HSQLDB server";
0637: }
0638:
0639: /**
0640: * Retrieves the server's product version, as a String. <p>
0641: *
0642: * Typically, this will be something like: "1.x.x" or "2.x.x" and so on.
0643: *
0644: * @return the product version of the server
0645: *
0646: * @jmx.managed-attribute
0647: * access="read-only"
0648: * description="Of Server"
0649: */
0650: public String getProductVersion() {
0651: return HsqlDatabaseProperties.THIS_VERSION;
0652: }
0653:
0654: /**
0655: * Retrieves a string respresentaion of the network protocol
0656: * this server offers, typically one of 'HTTP', HTTPS', 'HSQL' or 'HSQLS'.
0657: *
0658: * @return string respresentation of this server's protocol
0659: *
0660: * @jmx.managed-attribute
0661: * access="read-only"
0662: * description="Used to handle connections"
0663: */
0664: public String getProtocol() {
0665: return isTls() ? "HSQLS" : "HSQL";
0666: }
0667:
0668: /**
0669: * Retrieves a Throwable indicating the last server error, if any. <p>
0670: *
0671: * @return a Throwable indicating the last server error
0672: *
0673: * @jmx.managed-attribute
0674: * access="read-only"
0675: * description="Indicating last exception state"
0676: */
0677: public Throwable getServerError() {
0678: return serverError;
0679: }
0680:
0681: /**
0682: * Retrieves a String identifying this Server object.
0683: *
0684: * @return a String identifying this Server object
0685: *
0686: * @jmx.managed-attribute
0687: * access="read-only"
0688: * description="Identifying Server"
0689: */
0690: public String getServerId() {
0691: return serverId;
0692: }
0693:
0694: /**
0695: * Retrieves current state of this server in numerically coded form. <p>
0696: *
0697: * Typically, this will be one of: <p>
0698: *
0699: * <ol>
0700: * <li>ServerProperties.SERVER_STATE_ONLINE (1)
0701: * <li>ServerProperties.SERVER_STATE_OPENING (4)
0702: * <li>ServerProperties.SERVER_STATE_CLOSING (8)
0703: * <li>ServerProperties.SERVER_STATE_SHUTDOWN (16)
0704: * </ol>
0705: *
0706: * @return this server's state code.
0707: *
0708: * @jmx.managed-attribute
0709: * access="read-only"
0710: * description="1:ONLINE 4:OPENING 8:CLOSING, 16:SHUTDOWN"
0711: */
0712: public synchronized int getState() {
0713: return serverState;
0714: }
0715:
0716: /**
0717: * Retrieves a character sequence describing this server's current state,
0718: * including the message of the last exception, if there is one and it
0719: * is still in context.
0720: *
0721: * @return this server's state represented as a character sequence.
0722: *
0723: * @jmx.managed-attribute
0724: * access="read-only"
0725: * description="State as string"
0726: */
0727: public String getStateDescriptor() {
0728:
0729: String state;
0730: Throwable t = getServerError();
0731:
0732: switch (serverState) {
0733:
0734: case ServerConstants.SERVER_STATE_SHUTDOWN:
0735: state = "SHUTDOWN";
0736: break;
0737:
0738: case ServerConstants.SERVER_STATE_OPENING:
0739: state = "OPENING";
0740: break;
0741:
0742: case ServerConstants.SERVER_STATE_CLOSING:
0743: state = "CLOSING";
0744: break;
0745:
0746: case ServerConstants.SERVER_STATE_ONLINE:
0747: state = "ONLINE";
0748: break;
0749:
0750: default:
0751: state = "UNKNOWN";
0752: break;
0753: }
0754:
0755: return state;
0756: }
0757:
0758: /**
0759: * Retrieves the root context (directory) from which web content
0760: * is served. This property is relevant only when the server
0761: * protocol is HTTP(S). Although unlikely, it may be that in the future
0762: * other contexts, such as jar urls may be supported, so that pages can
0763: * be served from the contents of a jar or from the JVM class path.
0764: *
0765: * @return the root context (directory) from which web content is served
0766: *
0767: * @jmx.managed-attribute
0768: * access="read-write"
0769: * description="Context (directory)"
0770: */
0771: public String getWebRoot() {
0772: return "[IGNORED]";
0773: }
0774:
0775: /**
0776: * Assigns the specified socket to a new conection handler and
0777: * starts the handler in a new Thread.
0778: *
0779: * @param s the socket to connect
0780: */
0781: public void handleConnection(Socket s) {
0782:
0783: Thread t;
0784: Runnable r;
0785: String ctn;
0786:
0787: printWithThread("handleConnection(" + s + ") entered");
0788:
0789: if (!allowConnection(s)) {
0790: try {
0791: s.close();
0792: } catch (Exception e) {
0793: }
0794:
0795: printWithThread("allowConnection(): connection refused");
0796: printWithThread("handleConnection() exited");
0797:
0798: return;
0799: }
0800:
0801: // Maybe set up socket options, SSL
0802: // Session tracing/callbacks, etc.
0803: if (socketFactory != null) {
0804: socketFactory.configureSocket(s);
0805: }
0806:
0807: if (serverProtocol == ServerConstants.SC_PROTOCOL_HSQL) {
0808: r = new ServerConnection(s, this );
0809: ctn = ((ServerConnection) r).getConnectionThreadName();
0810:
0811: synchronized (serverConnSet) {
0812: serverConnSet.add(r);
0813: }
0814: } else {
0815: r = new WebServerConnection(s, (WebServer) this );
0816: ctn = ((WebServerConnection) r).getConnectionThreadName();
0817: }
0818:
0819: t = new Thread(serverConnectionThreadGroup, r, ctn);
0820:
0821: t.start();
0822: printWithThread("handleConnection() exited");
0823: }
0824:
0825: /**
0826: * Retrieves whether this server calls System.exit() when shutdown.
0827: *
0828: * @return true if this server does not call System.exit()
0829: *
0830: * @jmx.managed-attribute
0831: * access="read-write"
0832: * description="When Shutdown"
0833: */
0834: public boolean isNoSystemExit() {
0835: return serverProperties
0836: .isPropertyTrue(ServerConstants.SC_KEY_NO_SYSTEM_EXIT);
0837: }
0838:
0839: /**
0840: * Retrieves whether this server restarts on shutdown.
0841: *
0842: * @return true this server restarts on shutdown
0843: *
0844: * @jmx.managed-attribute
0845: * access="read-write"
0846: * description="Automatically?"
0847: */
0848: public boolean isRestartOnShutdown() {
0849: return serverProperties
0850: .isPropertyTrue(ServerConstants.SC_KEY_AUTORESTART_SERVER);
0851: }
0852:
0853: /**
0854: * Retrieves whether silent mode operation was requested in
0855: * the server properties.
0856: *
0857: * @return if true, silent mode was requested, else trace messages
0858: * are to be printed
0859: *
0860: * @jmx.managed-attribute
0861: * access="read-write"
0862: * description="No trace messages?"
0863: */
0864: public boolean isSilent() {
0865: return isSilent;
0866: }
0867:
0868: /**
0869: * Retrieves whether the use of secure sockets was requested in the
0870: * server properties.
0871: *
0872: * @return if true, secure sockets are requested, else not
0873: *
0874: * @jmx.managed-attribute
0875: * access="read-write"
0876: * description="Use TLS/SSL sockets?"
0877: */
0878: public boolean isTls() {
0879: return serverProperties
0880: .isPropertyTrue(ServerConstants.SC_KEY_TLS);
0881: }
0882:
0883: /**
0884: * Retrieves whether JDBC trace messages are to go to System.out or the
0885: * DriverManger PrintStream/PrintWriter, if any.
0886: *
0887: * @return true if tracing is on (JDBC trace messages to system out)
0888: *
0889: * @jmx.managed-attribute
0890: * access="read-write"
0891: * description="JDBC trace messages to System.out?"
0892: */
0893: public boolean isTrace() {
0894: return serverProperties
0895: .isPropertyTrue(ServerConstants.SC_KEY_TRACE);
0896: }
0897:
0898: /**
0899: * Attempts to put properties from the file
0900: * with the specified path. The file
0901: * extension '.properties' is implicit and should not
0902: * be included in the path specification.
0903: *
0904: * @param path the path of the desired properties file, without the
0905: * '.properties' file extension
0906: * @throws RuntimeException if this server is running
0907: * @return true if the indicated file was read sucessfully, else false
0908: *
0909: * @jmx.managed-operation
0910: * impact="ACTION"
0911: * description="Reads in properties"
0912: *
0913: * @jmx.managed-operation-parameter
0914: * name="path"
0915: * type="java.lang.String"
0916: * position="0"
0917: * description="(optional) returns false if path is empty"
0918: */
0919: public boolean putPropertiesFromFile(String path) {
0920:
0921: if (getState() != ServerConstants.SERVER_STATE_SHUTDOWN) {
0922: throw new RuntimeException();
0923: }
0924:
0925: path = FileUtil.canonicalOrAbsolutePath(path);
0926:
0927: HsqlProperties p = ServerConfiguration
0928: .getPropertiesFromFile(path);
0929:
0930: if (p == null || p.isEmpty()) {
0931: return false;
0932: }
0933:
0934: printWithThread("putPropertiesFromFile(): [" + path
0935: + ".properties]");
0936:
0937: try {
0938: setProperties(p);
0939: } catch (Exception e) {
0940: throw new RuntimeException("Failed to set properties: " + e);
0941: }
0942:
0943: return true;
0944: }
0945:
0946: /**
0947: * Puts properties from the supplied string argument. The relevant
0948: * key value pairs are the same as those for the (web)server.properties
0949: * file format, except that the 'server.' prefix should not be specified.
0950: *
0951: * @param s semicolon-delimited key=value pair string,
0952: * e.g. k1=v1;k2=v2;k3=v3...
0953: * @throws RuntimeException if this server is running
0954: *
0955: * @jmx.managed-operation
0956: * impact="ACTION"
0957: * description="'server.' key prefix automatically supplied"
0958: *
0959: * @jmx.managed-operation-parameter
0960: * name="s"
0961: * type="java.lang.String"
0962: * position="0"
0963: * description="semicolon-delimited key=value pairs"
0964: */
0965: public void putPropertiesFromString(String s) {
0966:
0967: if (getState() != ServerConstants.SERVER_STATE_SHUTDOWN) {
0968: throw new RuntimeException();
0969: }
0970:
0971: if (StringUtil.isEmpty(s)) {
0972: return;
0973: }
0974:
0975: printWithThread("putPropertiesFromString(): [" + s + "]");
0976:
0977: HsqlProperties p = HsqlProperties.delimitedArgPairsToProps(s,
0978: "=", ";", ServerConstants.SC_KEY_PREFIX);
0979:
0980: try {
0981: setProperties(p);
0982: } catch (Exception e) {
0983: throw new RuntimeException("Failed to set properties: " + e);
0984: }
0985: }
0986:
0987: /**
0988: * Sets the InetAddress with which this server's ServerSocket will be
0989: * constructed. A null or empty string or the special value "0.0.0.0"
0990: * can be used to bypass explicit selection, causing the ServerSocket
0991: * to be constructed without specifying an InetAddress.
0992: *
0993: * @param address A string representing the desired InetAddress as would
0994: * be retrieved by InetAddres.getByName(), or a null or empty string
0995: * or "0.0.0.0" to signify that the server socket should be constructed
0996: * using the signature that does not specify the InetAddress.
0997: * @throws RuntimeException if this server is running
0998: *
0999: * @jmx.managed-attribute
1000: */
1001: public void setAddress(String address) throws RuntimeException {
1002:
1003: checkRunning(false);
1004:
1005: if (org.hsqldb.lib.StringUtil.isEmpty(address)) {
1006: address = ServerConstants.SC_DEFAULT_ADDRESS;
1007: }
1008:
1009: printWithThread("setAddress(" + address + ")");
1010: serverProperties.setProperty(ServerConstants.SC_KEY_ADDRESS,
1011: address);
1012: }
1013:
1014: /**
1015: * Sets the external name (url alias) of the i'th hosted database.
1016: *
1017: * @param name external name (url alias) of the i'th HSQLDB database
1018: * instance this server is to host.
1019: * @throws RuntimeException if this server is running
1020: *
1021: * @jmx.managed-operation
1022: * impact="ACTION"
1023: * description="Sets the url alias by which is known the i'th hosted Database"
1024: *
1025: * @jmx.managed-operation-parameter
1026: * name="index"
1027: * type="int"
1028: * position="0"
1029: * description="This Server's index for the hosted Database"
1030: *
1031: * @jmx.managed-operation-parameter
1032: * name="name"
1033: * type="java.lang.String"
1034: * position="1"
1035: * description="url alias component for the hosted Database"
1036: */
1037: public void setDatabaseName(int index, String name)
1038: throws RuntimeException {
1039:
1040: checkRunning(false);
1041: printWithThread("setDatabaseName(" + index + "," + name + ")");
1042: serverProperties.setProperty(ServerConstants.SC_KEY_DBNAME
1043: + "." + index, name);
1044: }
1045:
1046: /**
1047: * Sets the path of the hosted database.
1048: *
1049: * @param path The path of the i'th HSQLDB database instance this server
1050: * is to host.
1051: *
1052: * @jmx.managed-operation
1053: * impact="ACTION"
1054: * description="Sets the database uri path for the i'th hosted Database"
1055: *
1056: * @jmx.managed-operation-parameter
1057: * name="index"
1058: * type="int"
1059: * position="0"
1060: * description="This Server's index for the hosted Database"
1061: *
1062: * @jmx.managed-operation-parameter
1063: * name="path"
1064: * type="java.lang.String"
1065: * position="1"
1066: * description="database uri path of the hosted Database"
1067: */
1068: public void setDatabasePath(int index, String path)
1069: throws RuntimeException {
1070:
1071: checkRunning(false);
1072: printWithThread("setDatabasePath(" + index + "," + path + ")");
1073: serverProperties.setProperty(ServerConstants.SC_KEY_DATABASE
1074: + "." + index, path);
1075: }
1076:
1077: /**
1078: * Sets the name of the web page served when no page is specified.
1079: *
1080: * @param file the name of the web page served when no page is specified
1081: *
1082: * @jmx.managed-attribute
1083: */
1084: public void setDefaultWebPage(String file) {
1085:
1086: checkRunning(false);
1087: printWithThread("setDefaultWebPage(" + file + ")");
1088:
1089: if (serverProtocol != ServerConstants.SC_PROTOCOL_HTTP) {
1090: return;
1091: }
1092:
1093: serverProperties.setProperty(
1094: ServerConstants.SC_KEY_WEB_DEFAULT_PAGE, file);
1095: }
1096:
1097: /**
1098: * Sets the server listen port.
1099: *
1100: * @param port the port at which this server listens
1101: *
1102: * @jmx.managed-attribute
1103: */
1104: public void setPort(int port) throws RuntimeException {
1105:
1106: checkRunning(false);
1107: printWithThread("setPort(" + port + ")");
1108: serverProperties.setProperty(ServerConstants.SC_KEY_PORT, port);
1109: }
1110:
1111: /**
1112: * Sets the PrintWriter to which server errors are logged. <p>
1113: *
1114: * Setting this attribute to null disables server error logging
1115: *
1116: * @param pw the PrintWriter to which server messages are logged
1117: */
1118: public void setErrWriter(PrintWriter pw) {
1119: errWriter = pw;
1120: }
1121:
1122: /**
1123: * Sets the PrintWriter to which server messages are logged. <p>
1124: *
1125: * Setting this attribute to null disables server message logging
1126: *
1127: * @param pw the PrintWriter to which server messages are logged
1128: */
1129: public void setLogWriter(PrintWriter pw) {
1130: logWriter = pw;
1131: }
1132:
1133: /**
1134: * Sets whether this server calls System.exit() when shutdown.
1135: *
1136: * @param noExit if true, System.exit() will not be called.
1137: *
1138: * @jmx.managed-attribute
1139: */
1140: public void setNoSystemExit(boolean noExit) {
1141:
1142: printWithThread("setNoSystemExit(" + noExit + ")");
1143: serverProperties.setProperty(
1144: ServerConstants.SC_KEY_NO_SYSTEM_EXIT, noExit);
1145: }
1146:
1147: /**
1148: * Sets whether this server restarts on shutdown.
1149: *
1150: * @param restart if true, this server restarts on shutdown
1151: *
1152: * @jmx.managed-attribute
1153: */
1154: public void setRestartOnShutdown(boolean restart) {
1155:
1156: printWithThread("setRestartOnShutdown(" + restart + ")");
1157: serverProperties.setProperty(
1158: ServerConstants.SC_KEY_AUTORESTART_SERVER, restart);
1159: }
1160:
1161: /**
1162: * Sets silent mode operation
1163: *
1164: * @param silent if true, then silent mode, else trace messages
1165: * are to be printed
1166: *
1167: * @jmx.managed-attribute
1168: */
1169: public void setSilent(boolean silent) {
1170:
1171: printWithThread("setSilent(" + silent + ")");
1172: serverProperties.setProperty(ServerConstants.SC_KEY_SILENT,
1173: silent);
1174:
1175: isSilent = silent;
1176: }
1177:
1178: /**
1179: * Sets whether to use secure sockets
1180: *
1181: * @param tls true for secure sockets, else false
1182: * @throws RuntimeException if this server is running
1183: *
1184: * @jmx.managed-attribute
1185: */
1186: public void setTls(boolean tls) {
1187:
1188: checkRunning(false);
1189: printWithThread("setTls(" + tls + ")");
1190: serverProperties.setProperty(ServerConstants.SC_KEY_TLS, tls);
1191: }
1192:
1193: /**
1194: * Sets whether trace messages go to System.out or the
1195: * DriverManger PrintStream/PrintWriter, if any.
1196: *
1197: * @param trace if true, route JDBC trace messages to System.out
1198: *
1199: * @jmx.managed-attribute
1200: */
1201: public void setTrace(boolean trace) {
1202:
1203: printWithThread("setTrace(" + trace + ")");
1204: serverProperties.setProperty(ServerConstants.SC_KEY_TRACE,
1205: trace);
1206: JavaSystem.setLogToSystem(trace);
1207: }
1208:
1209: /**
1210: * Sets the path of the root directory from which web content is served.
1211: *
1212: * @param root the root (context) directory from which web content
1213: * is served
1214: *
1215: * @jmx.managed-attribute
1216: */
1217: public void setWebRoot(String root) {
1218:
1219: checkRunning(false);
1220:
1221: root = (new File(root)).getAbsolutePath();
1222:
1223: printWithThread("setWebRoot(" + root + ")");
1224:
1225: if (serverProtocol != ServerConstants.SC_PROTOCOL_HTTP) {
1226: return;
1227: }
1228:
1229: serverProperties.setProperty(ServerConstants.SC_KEY_WEB_ROOT,
1230: root);
1231: }
1232:
1233: /**
1234: * Sets server properties using the specified properties object
1235: *
1236: * @param p The object containing properties to set
1237: */
1238: public void setProperties(HsqlProperties p) {
1239:
1240: checkRunning(false);
1241:
1242: if (p != null) {
1243: serverProperties.addProperties(p);
1244: ServerConfiguration
1245: .translateAddressProperty(serverProperties);
1246: }
1247:
1248: maxConnections = serverProperties.getIntegerProperty(
1249: ServerConstants.SC_KEY_MAX_CONNECTIONS, 16);
1250:
1251: JavaSystem.setLogToSystem(isTrace());
1252:
1253: isSilent = serverProperties
1254: .isPropertyTrue(ServerConstants.SC_KEY_SILENT);
1255: isRemoteOpen = serverProperties
1256: .isPropertyTrue(ServerConstants.SC_KEY_REMOTE_OPEN_DB);
1257: }
1258:
1259: /**
1260: * Starts this server synchronously. <p>
1261: *
1262: * This method waits for current state to change from
1263: * SERVER_STATE_OPENNING. In order to discover the success or failure
1264: * of this operation, server state must be polled or a subclass of Server
1265: * must be used that overrides the setState method to provide state
1266: * change notification.
1267: *
1268: * @return the server state noted at entry to this method
1269: *
1270: * @jmx.managed-operation
1271: * impact="ACTION_INFO"
1272: * description="Invokes asynchronous startup sequence; returns previous state"
1273: */
1274: public int start() {
1275:
1276: printWithThread("start() entered");
1277:
1278: int previousState = getState();
1279:
1280: if (serverThread != null) {
1281: printWithThread("start(): serverThread != null; no action taken");
1282:
1283: return previousState;
1284: }
1285:
1286: setState(ServerConstants.SERVER_STATE_OPENING);
1287:
1288: serverThread = new ServerThread("HSQLDB Server ");
1289:
1290: serverThread.start();
1291:
1292: // call synchronized getState() to become owner of the Server Object's monitor
1293: while (getState() == ServerConstants.SERVER_STATE_OPENING) {
1294: try {
1295: Thread.sleep(100);
1296: } catch (InterruptedException e) {
1297: }
1298: }
1299:
1300: printWithThread("start() exiting");
1301:
1302: return previousState;
1303: }
1304:
1305: /**
1306: * Stops this server asynchronously. <p>
1307: *
1308: * This method returns immediately, regardless of current state. In order
1309: * to discover the success or failure of this operation, server state must
1310: * be polled or a subclass of Server must be used that overrides the
1311: * setState method to provide state change notification.
1312: *
1313: * @return the server state noted at entry to this method
1314: *
1315: * @jmx.managed-operation
1316: * impact="ACTION_INFO"
1317: * description="Invokes asynchronous shutdown sequence; returns previous state"
1318: */
1319: public int stop() {
1320:
1321: printWithThread("stop() entered");
1322:
1323: int previousState = getState();
1324:
1325: if (serverThread == null) {
1326: printWithThread("stop() serverThread is null; no action taken");
1327:
1328: return previousState;
1329: }
1330:
1331: releaseServerSocket();
1332: printWithThread("stop() exiting");
1333:
1334: return previousState;
1335: }
1336:
1337: /**
1338: * Retrieves whether the specified socket should be allowed
1339: * to make a connection. By default, this method always returns
1340: * true, but it can be overidden to implement hosts allow-deny
1341: * functionality.
1342: *
1343: * @param socket the socket to test.
1344: */
1345: protected boolean allowConnection(Socket socket) {
1346: return true;
1347: }
1348:
1349: /**
1350: * Initializes this server, setting the accepted connection protocol.
1351: *
1352: * @param protocol typically either SC_PROTOCOL_HTTP or SC_PROTOCOL_HSQL
1353: */
1354: protected void init(int protocol) {
1355:
1356: // PRE: This method is only called from the constructor
1357: serverState = ServerConstants.SERVER_STATE_SHUTDOWN;
1358: serverConnSet = new HashSet();
1359: serverId = toString();
1360: serverId = serverId.substring(serverId.lastIndexOf('.') + 1);
1361: serverProtocol = protocol;
1362: serverProperties = ServerConfiguration
1363: .newDefaultProperties(protocol);
1364: logWriter = new PrintWriter(System.out);
1365: errWriter = new PrintWriter(System.err);
1366:
1367: JavaSystem.setLogToSystem(isTrace());
1368: }
1369:
1370: /**
1371: * Sets the server state value.
1372: *
1373: * @param state the new value
1374: */
1375: protected synchronized void setState(int state) {
1376: serverState = state;
1377: }
1378:
1379: /**
1380: * This is called from org.hsqldb.DatabaseManager when a database is
1381: * shutdown. This shuts the server down if it is the last database
1382: *
1383: * @param action a code indicating what has happend
1384: */
1385: final void notify(int action, int id) {
1386:
1387: printWithThread("notifiy(" + action + "," + id + ") entered");
1388:
1389: if (action != ServerConstants.SC_DATABASE_SHUTDOWN) {
1390: return;
1391: }
1392:
1393: releaseDatabase(id);
1394:
1395: boolean shutdown = true;
1396:
1397: for (int i = 0; i < dbID.length; i++) {
1398: if (dbAlias[i] != null) {
1399: shutdown = false;
1400: }
1401: }
1402:
1403: if (!isRemoteOpen && shutdown) {
1404: stop();
1405: }
1406: }
1407:
1408: /**
1409: * This releases the resources used for a database.
1410: * Is called with id 0 multiple times for non-existent databases
1411: */
1412: final synchronized void releaseDatabase(int id) {
1413:
1414: Iterator it;
1415: boolean found = false;
1416:
1417: printWithThread("releaseDatabase(" + id + ") entered");
1418:
1419: // check all slots as a database may be opened by multiple aliases
1420: for (int i = 0; i < dbID.length; i++) {
1421: if (dbID[i] == id && dbAlias[i] != null) {
1422: dbID[i] = 0;
1423: dbAlias[i] = null;
1424: dbPath[i] = null;
1425: dbType[i] = null;
1426: dbProps[i] = null;
1427: }
1428: }
1429:
1430: synchronized (serverConnSet) {
1431: it = new WrapperIterator(serverConnSet.toArray(null));
1432: }
1433:
1434: while (it.hasNext()) {
1435: ServerConnection sc = (ServerConnection) it.next();
1436:
1437: if (sc.dbID == id) {
1438: sc.signalClose();
1439: serverConnSet.remove(sc);
1440: }
1441: }
1442:
1443: printWithThread("releaseDatabase(" + id + ") exiting");
1444: }
1445:
1446: /**
1447: * Prints the specified message, s, formatted to identify that the print
1448: * operation is against this server instance.
1449: *
1450: * @param msg The message to print
1451: */
1452: protected synchronized void print(String msg) {
1453:
1454: PrintWriter writer = logWriter;
1455:
1456: if (writer != null) {
1457: writer.println("[" + serverId + "]: " + msg);
1458: writer.flush();
1459: }
1460: }
1461:
1462: /**
1463: * Prints value from server's resource bundle, formatted to
1464: * identify that the print operation is against this server instance.
1465: * Value may be localized according to the default JVM locale
1466: *
1467: * @param key the resource key
1468: */
1469: final void printResource(String key) {
1470:
1471: String resource;
1472: StringTokenizer st;
1473:
1474: if (serverBundleHandle < 0) {
1475: return;
1476: }
1477:
1478: resource = BundleHandler.getString(serverBundleHandle, key);
1479:
1480: if (resource == null) {
1481: return;
1482: }
1483:
1484: st = new StringTokenizer(resource, "\n\r");
1485:
1486: while (st.hasMoreTokens()) {
1487: print(st.nextToken());
1488: }
1489: }
1490:
1491: /**
1492: * Prints the stack trace of the Throwable, t, to this Server object's
1493: * errWriter. <p>
1494: *
1495: * @param t the Throwable whose stack trace is to be printed
1496: */
1497: protected synchronized void printStackTrace(Throwable t) {
1498:
1499: if (errWriter != null) {
1500: t.printStackTrace(errWriter);
1501: errWriter.flush();
1502: }
1503: }
1504:
1505: /**
1506: * Prints the specified message, s, prepended with a timestamp representing
1507: * the current date and time, formatted to identify that the print
1508: * operation is against this server instance.
1509: *
1510: * @param msg the message to print
1511: */
1512: final void printWithTimestamp(String msg) {
1513: print(HsqlDateTime.getSytemTimeString() + " " + msg);
1514: }
1515:
1516: /**
1517: * Prints a message formatted similarly to print(String), additionally
1518: * identifying the current (calling) thread. Replaces old method
1519: * trace(String msg).
1520: *
1521: * @param msg the message to print
1522: */
1523: protected void printWithThread(String msg) {
1524:
1525: if (!isSilent()) {
1526: print("[" + Thread.currentThread() + "]: " + msg);
1527: }
1528: }
1529:
1530: /**
1531: * Prints an error message to this Server object's errWriter.
1532: * The message is formatted similarly to print(String),
1533: * additionally identifying the current (calling) thread.
1534: *
1535: * @param msg the message to print
1536: */
1537: protected synchronized void printError(String msg) {
1538:
1539: PrintWriter writer = errWriter;
1540:
1541: if (writer != null) {
1542: writer.print("[" + serverId + "]: ");
1543: writer.print("[" + Thread.currentThread() + "]: ");
1544: writer.println(msg);
1545: writer.flush();
1546: }
1547: }
1548:
1549: /**
1550: * Prints a description of the request encapsulated by the
1551: * Result argument, r.
1552: *
1553: * Printing occurs iff isSilent() is false. <p>
1554: *
1555: * The message is formatted similarly to print(String), additionally
1556: * indicating the connection identifier. <p>
1557: *
1558: * For Server instances, cid is typically the value assigned to each
1559: * ServerConnection object that is unique amongst all such identifiers
1560: * in each distinct JVM session / class loader
1561: * context. <p>
1562: *
1563: * For WebServer instances, a single logical connection actually spawns
1564: * a new physical WebServerConnection object for each request, so the
1565: * cid is typically the underlying session id, since that does not
1566: * change for the duration of the logical connection.
1567: *
1568: * @param cid the connection identifier
1569: * @param r the request whose description is to be printed
1570: */
1571: final void printRequest(int cid, Result r) {
1572:
1573: if (isSilent()) {
1574: return;
1575: }
1576:
1577: StringBuffer sb = new StringBuffer();
1578:
1579: sb.append(cid);
1580: sb.append(':');
1581:
1582: switch (r.mode) {
1583:
1584: case ResultConstants.SQLPREPARE: {
1585: sb.append("SQLCLI:SQLPREPARE ");
1586: sb.append(r.getMainString());
1587:
1588: break;
1589: }
1590: case ResultConstants.SQLEXECDIRECT: {
1591: if (r.getSize() < 2) {
1592: sb.append(r.getMainString());
1593: } else {
1594: sb.append("SQLCLI:SQLEXECDIRECT:BATCHMODE\n");
1595:
1596: Iterator it = r.iterator();
1597:
1598: while (it.hasNext()) {
1599: Object[] data = (Object[]) it.next();
1600:
1601: sb.append(data[0]).append('\n');
1602: }
1603: }
1604:
1605: break;
1606: }
1607: case ResultConstants.SQLEXECUTE: {
1608: sb.append("SQLCLI:SQLEXECUTE:");
1609:
1610: if (r.getSize() > 1) {
1611: sb.append("BATCHMODE:");
1612: }
1613:
1614: sb.append(r.getStatementID());
1615:
1616: /**
1617: * todo - fredt - NOW - fix this without appendStringValueOf
1618: */
1619: /*
1620: if (r.getSize() == 1) {
1621: sb.append('\n');
1622: StringUtil.appendStringValueOf(r.getParameterData(), sb, true);
1623: }
1624: */
1625: break;
1626: }
1627: case ResultConstants.SQLFREESTMT: {
1628: sb.append("SQLCLI:SQLFREESTMT:");
1629: sb.append(r.getStatementID());
1630:
1631: break;
1632: }
1633: case ResultConstants.GETSESSIONATTR: {
1634: sb.append("HSQLCLI:GETSESSIONATTR");
1635:
1636: break;
1637: }
1638: case ResultConstants.SETSESSIONATTR: {
1639: sb.append("HSQLCLI:SETSESSIONATTR:");
1640: sb.append("AUTOCOMMIT ");
1641: sb.append(r.rRoot.data[Session.INFO_AUTOCOMMIT]);
1642: sb.append(" CONNECTION_READONLY ");
1643: sb.append(r.rRoot.data[Session.INFO_CONNECTION_READONLY]);
1644:
1645: break;
1646: }
1647: case ResultConstants.SQLENDTRAN: {
1648: sb.append("SQLCLI:SQLENDTRAN:");
1649:
1650: switch (r.getEndTranType()) {
1651:
1652: case ResultConstants.COMMIT:
1653: sb.append("COMMIT");
1654: break;
1655:
1656: case ResultConstants.ROLLBACK:
1657: sb.append("ROLLBACK");
1658: break;
1659:
1660: case ResultConstants.SAVEPOINT_NAME_RELEASE:
1661: sb.append("SAVEPOINT_NAME_RELEASE ");
1662: sb.append(r.getMainString());
1663: break;
1664:
1665: case ResultConstants.SAVEPOINT_NAME_ROLLBACK:
1666: sb.append("SAVEPOINT_NAME_ROLLBACK ");
1667: sb.append(r.getMainString());
1668: break;
1669:
1670: default:
1671: sb.append(r.getEndTranType());
1672: }
1673:
1674: break;
1675: }
1676: case ResultConstants.SQLSTARTTRAN: {
1677: sb.append("SQLCLI:SQLSTARTTRAN");
1678:
1679: break;
1680: }
1681: case ResultConstants.SQLDISCONNECT: {
1682: sb.append("SQLCLI:SQLDISCONNECT");
1683:
1684: break;
1685: }
1686: case ResultConstants.SQLSETCONNECTATTR: {
1687: sb.append("SQLCLI:SQLSETCONNECTATTR:");
1688:
1689: switch (r.getConnectionAttrType()) {
1690:
1691: case ResultConstants.SQL_ATTR_SAVEPOINT_NAME: {
1692: sb.append("SQL_ATTR_SAVEPOINT_NAME ");
1693: sb.append(r.getMainString());
1694:
1695: break;
1696: }
1697: default: {
1698: sb.append(r.getConnectionAttrType());
1699: }
1700: }
1701:
1702: break;
1703: }
1704: default: {
1705: sb.append("SQLCLI:MODE:");
1706: sb.append(r.mode);
1707:
1708: break;
1709: }
1710: }
1711:
1712: print(sb.toString());
1713: }
1714:
1715: /**
1716: * return database ID
1717: */
1718: synchronized final int getDBID(String aliasPath)
1719: throws HsqlException {
1720:
1721: int semipos = aliasPath.indexOf(';');
1722: String alias = aliasPath;
1723: String filepath = null;
1724:
1725: if (semipos != -1) {
1726: alias = aliasPath.substring(0, semipos);
1727: filepath = aliasPath.substring(semipos + 1);
1728: }
1729:
1730: int dbIndex = ArrayUtil.find(dbAlias, alias);
1731:
1732: if (dbIndex == -1) {
1733: if (filepath == null) {
1734: RuntimeException e = new RuntimeException(
1735: "database alias does not exist");
1736:
1737: printError("database alias=" + alias
1738: + " does not exist");
1739: setServerError(e);
1740:
1741: throw e;
1742: } else {
1743: return openDatabase(alias, filepath);
1744: }
1745: } else {
1746: return dbID[dbIndex];
1747: }
1748: }
1749:
1750: /**
1751: * Open and return database ID
1752: */
1753: final int openDatabase(String alias, String filepath)
1754: throws HsqlException {
1755:
1756: if (!isRemoteOpen) {
1757: RuntimeException e = new RuntimeException(
1758: "remote open not allowed");
1759:
1760: printError("Remote database open not allowed");
1761: setServerError(e);
1762:
1763: throw e;
1764: }
1765:
1766: int i = getFirstEmptyDatabaseIndex();
1767:
1768: if (i < -1) {
1769: RuntimeException e = new RuntimeException(
1770: "limit of open databases reached");
1771:
1772: printError("limit of open databases reached");
1773: setServerError(e);
1774:
1775: throw e;
1776: }
1777:
1778: HsqlProperties newprops = DatabaseURL.parseURL(filepath, false);
1779:
1780: if (newprops == null) {
1781: RuntimeException e = new RuntimeException(
1782: "invalid database path");
1783:
1784: printError("invalid database path");
1785: setServerError(e);
1786:
1787: throw e;
1788: }
1789:
1790: String path = newprops.getProperty("database");
1791: String type = newprops.getProperty("connection_type");
1792:
1793: try {
1794: int dbid = DatabaseManager.getDatabase(type, path, this ,
1795: newprops);
1796:
1797: dbID[i] = dbid;
1798: dbAlias[i] = alias;
1799: dbPath[i] = path;
1800: dbType[i] = type;
1801: dbProps[i] = newprops;
1802:
1803: return dbid;
1804: } catch (HsqlException e) {
1805: printError("Database [index=" + i + "db=" + dbType[i]
1806: + dbPath[i] + ", alias=" + dbAlias[i]
1807: + "] did not open: " + e.toString());
1808: setServerError(e);
1809:
1810: throw e;
1811: }
1812: }
1813:
1814: final int getFirstEmptyDatabaseIndex() {
1815:
1816: for (int i = 0; i < dbAlias.length; i++) {
1817: if (dbAlias[i] == null) {
1818: return i;
1819: }
1820: }
1821:
1822: return -1;
1823: }
1824:
1825: /**
1826: * Opens this server's database instances. This method returns true If
1827: * at least one database goes online, otherwise it returns false.
1828: *
1829: * If openning any of the databases is attempted and an exception is
1830: * thrown, the server error is set to this exception.
1831: *
1832: * @throws HsqlException if a database access error occurs
1833: */
1834: final boolean openDatabases() {
1835:
1836: printWithThread("openDatabases() entered");
1837:
1838: boolean success = false;
1839:
1840: setDBInfoArrays();
1841:
1842: for (int i = 0; i < dbAlias.length; i++) {
1843: if (dbAlias[i] == null) {
1844: continue;
1845: }
1846:
1847: printWithThread("Opening database: [" + dbType[i]
1848: + dbPath[i] + "]");
1849:
1850: StopWatch sw = new StopWatch();
1851: int id;
1852:
1853: try {
1854: id = DatabaseManager.getDatabase(dbType[i], dbPath[i],
1855: this , dbProps[i]);
1856: dbID[i] = id;
1857: success = true;
1858: } catch (HsqlException e) {
1859: printError("Database [index=" + i + "db=" + dbType[i]
1860: + dbPath[i] + ", alias=" + dbAlias[i]
1861: + "] did not open: " + e.toString());
1862: setServerError(e);
1863:
1864: dbAlias[i] = null;
1865: dbPath[i] = null;
1866: dbType[i] = null;
1867: dbProps[i] = null;
1868:
1869: continue;
1870: }
1871:
1872: sw.stop();
1873:
1874: String msg = "Database [index=" + i + ", id=" + id + ", "
1875: + "db=" + dbType[i] + dbPath[i] + ", alias="
1876: + dbAlias[i] + "] opened sucessfully";
1877:
1878: print(sw.elapsedTimeToMessage(msg));
1879: }
1880:
1881: printWithThread("openDatabases() exiting");
1882:
1883: if (isRemoteOpen) {
1884: success = true;
1885: }
1886:
1887: if (!success && getServerError() == null) {
1888:
1889: // database alias / path list is empty or without full info for any DB
1890: setServerError(Trace.error(Trace.SERVER_NO_DATABASE));
1891: }
1892:
1893: return success;
1894: }
1895:
1896: /**
1897: * Initialises the database attributes lists from the server properties object.
1898: */
1899: private void setDBInfoArrays() {
1900:
1901: dbAlias = getDBNameArray();
1902: dbPath = new String[dbAlias.length];
1903: dbType = new String[dbAlias.length];
1904: dbID = new int[dbAlias.length];
1905: dbProps = new HsqlProperties[dbAlias.length];
1906:
1907: for (int i = 0; i < dbAlias.length; i++) {
1908: if (dbAlias[i] == null) {
1909: continue;
1910: }
1911:
1912: String path = getDatabasePath(i, true);
1913:
1914: if (path == null) {
1915: dbAlias[i] = null;
1916:
1917: continue;
1918: }
1919:
1920: HsqlProperties dbURL = DatabaseURL.parseURL(path, false);
1921:
1922: if (dbURL == null) {
1923: dbAlias[i] = null;
1924:
1925: continue;
1926: }
1927:
1928: dbPath[i] = dbURL.getProperty("database");
1929: dbType[i] = dbURL.getProperty("connection_type");
1930: dbProps[i] = dbURL;
1931: }
1932: }
1933:
1934: /**
1935: * Returns a possibly sparse array of all server.dbname.n values
1936: * from the properties object.
1937: */
1938: private String[] getDBNameArray() {
1939:
1940: final String prefix = ServerConstants.SC_KEY_DBNAME + ".";
1941: final int prefixLen = prefix.length();
1942: String[] dblist = new String[10];
1943: int maxindex = 0;
1944:
1945: try {
1946: Enumeration en = serverProperties.propertyNames();
1947:
1948: for (; en.hasMoreElements();) {
1949: String key = (String) en.nextElement();
1950:
1951: if (!key.startsWith(prefix)) {
1952: continue;
1953: }
1954:
1955: try {
1956: int dbnum = Integer.parseInt(key
1957: .substring(prefixLen));
1958:
1959: maxindex = dbnum < maxindex ? maxindex : dbnum;
1960: dblist[dbnum] = serverProperties.getProperty(key)
1961: .toLowerCase();
1962: } catch (NumberFormatException e) {
1963: printWithThread("dblist: " + e.toString());
1964: }
1965: }
1966: } catch (ArrayIndexOutOfBoundsException e) {
1967: printWithThread("dblist: " + e.toString());
1968: }
1969:
1970: return dblist;
1971: }
1972:
1973: /**
1974: * Constructs and installs a new ServerSocket instance for this server.
1975: *
1976: * @throws Exception if it is not possible to construct and install
1977: * a new ServerSocket
1978: */
1979: private void openServerSocket() throws Exception {
1980:
1981: String address;
1982: int port;
1983: String[] candidateAddrs;
1984: String emsg;
1985: StopWatch sw;
1986:
1987: printWithThread("openServerSocket() entered");
1988:
1989: if (isTls()) {
1990: printWithThread("Requesting TLS/SSL-encrypted JDBC");
1991: }
1992:
1993: sw = new StopWatch();
1994: socketFactory = HsqlSocketFactory.getInstance(isTls());
1995: address = getAddress();
1996: port = getPort();
1997:
1998: if (org.hsqldb.lib.StringUtil.isEmpty(address)
1999: || ServerConstants.SC_DEFAULT_ADDRESS
2000: .equalsIgnoreCase(address.trim())) {
2001: socket = socketFactory.createServerSocket(port);
2002: } else {
2003: try {
2004: socket = socketFactory
2005: .createServerSocket(port, address);
2006: } catch (UnknownHostException e) {
2007: candidateAddrs = ServerConfiguration
2008: .listLocalInetAddressNames();
2009:
2010: int messageID;
2011: Object[] messageParameters;
2012:
2013: if (candidateAddrs.length > 0) {
2014: messageID = Trace.Server_openServerSocket;
2015: messageParameters = new Object[] { address,
2016: candidateAddrs };
2017: } else {
2018: messageID = Trace.Server_openServerSocket2;
2019: messageParameters = new Object[] { address };
2020: }
2021:
2022: throw new UnknownHostException(Trace.getMessage(
2023: messageID, true, messageParameters));
2024: }
2025: }
2026:
2027: /*
2028: * Following line necessary for Java 1.3 on UNIX. See accept()
2029: * comment elsewhere in this file.
2030: */
2031: socket.setSoTimeout(1000);
2032: printWithThread("Got server socket: " + socket);
2033: print(sw
2034: .elapsedTimeToMessage("Server socket opened successfully"));
2035:
2036: if (socketFactory.isSecure()) {
2037: print("Using TLS/SSL-encrypted JDBC");
2038: }
2039:
2040: printWithThread("openServerSocket() exiting");
2041: }
2042:
2043: /** Prints a timestamped message indicating that this server is online */
2044: private void printServerOnlineMessage() {
2045:
2046: String s = getProductName() + " " + getProductVersion()
2047: + " is online";
2048:
2049: printWithTimestamp(s);
2050: printResource("online.help");
2051: }
2052:
2053: /**
2054: * Prints a description of the server properties iff !isSilent().
2055: */
2056: protected void printProperties() {
2057:
2058: Enumeration e;
2059: String key;
2060: String value;
2061:
2062: // Avoid the waste of generating each description,
2063: // only for trace() to silently discard it
2064: if (isSilent()) {
2065: return;
2066: }
2067:
2068: e = serverProperties.propertyNames();
2069:
2070: while (e.hasMoreElements()) {
2071: key = (String) e.nextElement();
2072: value = serverProperties.getProperty(key);
2073:
2074: printWithThread(key + "=" + value);
2075: }
2076: }
2077:
2078: /**
2079: * Puts this server into the SERVER_CLOSING state, closes the ServerSocket
2080: * and nullifies the reference to it. If the ServerSocket is already null,
2081: * this method exists immediately, otherwise, the result is to fully
2082: * shut down the server.
2083: */
2084: private void releaseServerSocket() {
2085:
2086: printWithThread("releaseServerSocket() entered");
2087:
2088: if (socket != null) {
2089: printWithThread("Releasing server socket: [" + socket + "]");
2090: setState(ServerConstants.SERVER_STATE_CLOSING);
2091:
2092: try {
2093: socket.close();
2094: } catch (IOException e) {
2095: printError("Exception closing server socket");
2096: printError("releaseServerSocket(): " + e);
2097: }
2098:
2099: socket = null;
2100: }
2101:
2102: printWithThread("releaseServerSocket() exited");
2103: }
2104:
2105: /**
2106: * Attempts to bring this server fully online by opening
2107: * a new ServerSocket, obtaining the hosted databases,
2108: * notifying the status waiter thread (if any) and
2109: * finally entering the listen loop if all else succeeds.
2110: * If any part of the process fails, then this server enters
2111: * its shutdown sequence.
2112: */
2113: private void run() {
2114:
2115: StopWatch sw;
2116: ThreadGroup tg;
2117: String tgName;
2118:
2119: printWithThread("run() entered");
2120: print("Initiating startup sequence...");
2121: printProperties();
2122:
2123: sw = new StopWatch();
2124:
2125: setServerError(null);
2126:
2127: try {
2128:
2129: // Faster init first:
2130: // It is huge waste to fully open the databases, only
2131: // to find that the socket address is already in use
2132: openServerSocket();
2133: } catch (Exception e) {
2134: setServerError(e);
2135: printError("run()/openServerSocket(): ");
2136: printStackTrace(e);
2137: shutdown(true);
2138:
2139: return;
2140: }
2141:
2142: tgName = "HSQLDB Connections @"
2143: + Integer.toString(this .hashCode(), 16);
2144: tg = new ThreadGroup(tgName);
2145:
2146: tg.setDaemon(false);
2147:
2148: serverConnectionThreadGroup = tg;
2149:
2150: // Mount the databases this server is supposed to host.
2151: // This may take some time if the databases are not all
2152: // already open.
2153: if (openDatabases() == false) {
2154: setServerError(null);
2155: printError("Shutting down because there are no open databases");
2156: shutdown(true);
2157:
2158: return;
2159: }
2160:
2161: // At this point, we have a valid server socket and
2162: // a valid hosted database set, so its OK to start
2163: // listening for connections.
2164: setState(ServerConstants.SERVER_STATE_ONLINE);
2165: print(sw.elapsedTimeToMessage("Startup sequence completed"));
2166: printServerOnlineMessage();
2167:
2168: try {
2169: /*
2170: * This loop is necessary for UNIX w/ Sun Java 1.3 because
2171: * in that case the socket.close() elsewhere will not
2172: * interrupt this accept().
2173: */
2174: while (true) {
2175: try {
2176: handleConnection(socket.accept());
2177: } catch (java.io.InterruptedIOException e) {
2178: }
2179: }
2180: } catch (IOException e) {
2181: if (getState() == ServerConstants.SERVER_STATE_ONLINE) {
2182: setServerError(e);
2183: printError(this + ".run()/handleConnection(): ");
2184: printStackTrace(e);
2185: }
2186: } catch (Throwable t) {
2187: printWithThread(t.toString());
2188: } finally {
2189: shutdown(false); // or maybe getServerError() != null?
2190: }
2191: }
2192:
2193: /**
2194: * Sets this Server's last encountered error state.
2195: *
2196: * @param t The new value for the server error
2197: */
2198: protected void setServerError(Throwable t) {
2199: serverError = t;
2200: }
2201:
2202: /**
2203: * External method to shut down this server.
2204: */
2205: public void shutdown() {
2206: shutdown(false);
2207: }
2208:
2209: /**
2210: * Shuts down this server.
2211: *
2212: * @param error true if shutdown is in response to an error
2213: * state, else false
2214: */
2215: protected synchronized void shutdown(boolean error) {
2216:
2217: if (serverState == ServerConstants.SERVER_STATE_SHUTDOWN) {
2218: return;
2219: }
2220:
2221: StopWatch sw;
2222:
2223: printWithThread("shutdown() entered");
2224:
2225: sw = new StopWatch();
2226:
2227: print("Initiating shutdown sequence...");
2228: releaseServerSocket();
2229: DatabaseManager.deRegisterServer(this );
2230:
2231: if (dbPath != null) {
2232: for (int i = 0; i < dbPath.length; i++) {
2233: releaseDatabase(dbID[i]);
2234: }
2235: }
2236:
2237: // Be nice and let applications exit if there are no
2238: // running connection threads
2239: if (serverConnectionThreadGroup != null) {
2240: if (!serverConnectionThreadGroup.isDestroyed()) {
2241: for (int i = 0; serverConnectionThreadGroup
2242: .activeCount() > 0; i++) {
2243: int count;
2244:
2245: try {
2246: Thread.sleep(100);
2247: } catch (Exception e) {
2248:
2249: // e.getMessage();
2250: }
2251: }
2252:
2253: try {
2254: serverConnectionThreadGroup.destroy();
2255: printWithThread(serverConnectionThreadGroup
2256: .getName()
2257: + " destroyed");
2258: } catch (Throwable t) {
2259: printWithThread(serverConnectionThreadGroup
2260: .getName()
2261: + " not destroyed");
2262: printWithThread(t.toString());
2263: }
2264: }
2265:
2266: serverConnectionThreadGroup = null;
2267: }
2268:
2269: serverThread = null;
2270:
2271: setState(ServerConstants.SERVER_STATE_SHUTDOWN);
2272: print(sw.elapsedTimeToMessage("Shutdown sequence completed"));
2273:
2274: if (isNoSystemExit()) {
2275: printWithTimestamp("SHUTDOWN : System.exit() was not called");
2276: printWithThread("shutdown() exited");
2277: } else {
2278: printWithTimestamp("SHUTDOWN : System.exit() is called next");
2279: printWithThread("shutdown() exiting...");
2280:
2281: try {
2282: System.exit(0);
2283: } catch (Throwable t) {
2284: printWithThread(t.toString());
2285: }
2286: }
2287: }
2288:
2289: /**
2290: * Prints message for the specified key, without any special
2291: * formatting. The message content comes from the server
2292: * resource bundle and thus may localized according to the default
2293: * JVM locale.<p>
2294: *
2295: * Uses System.out directly instead of Trace.printSystemOut() so it
2296: * always prints, regardless of Trace settings.
2297: *
2298: * @param key for message
2299: */
2300: protected static void printHelp(String key) {
2301: System.out.println(BundleHandler.getString(serverBundleHandle,
2302: key));
2303: }
2304: }
|