001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.server;
007:
008: import java.io.IOException;
009: import java.net.ServerSocket;
010: import java.net.Socket;
011: import java.sql.Connection;
012: import java.sql.DriverManager;
013: import java.sql.PreparedStatement;
014: import java.sql.SQLException;
015: import java.sql.Statement;
016: import java.util.ArrayList;
017: import java.util.Collections;
018: import java.util.HashMap;
019: import java.util.HashSet;
020: import java.util.Map;
021: import java.util.Properties;
022: import java.util.Set;
023:
024: import org.h2.Driver;
025: import org.h2.engine.Constants;
026: import org.h2.message.Message;
027: import org.h2.message.TraceSystem;
028: import org.h2.util.JdbcUtils;
029: import org.h2.util.MathUtils;
030: import org.h2.util.NetUtils;
031:
032: /**
033: * The TCP server implements the native H2 database server protocol.
034: * It supports multiple client connections to multiple databases
035: * (many to many). The same database may be opened by multiple clients.
036: * Also supported is the mixed mode: opening databases in embedded mode,
037: * and at the same time start a TCP server to allow clients to connect to
038: * the same database over the network.
039: */
040: public class TcpServer implements Service {
041:
042: // TODO new feature: implement automatic client / server mode if 'socket'
043: // file locking is used
044: // TODO better exception message if the port is already in use, maybe
045: // automatically use the next free port?
046:
047: public static final int DEFAULT_PORT = 9092;
048: private static final int SHUTDOWN_NORMAL = 0;
049: private static final int SHUTDOWN_FORCE = 1;
050: public static boolean logInternalErrors;
051:
052: private int port;
053: private boolean log;
054: private boolean ssl;
055: private boolean stop;
056: private ServerSocket serverSocket;
057: private Set running = Collections.synchronizedSet(new HashSet());
058: private String baseDir;
059: private String url;
060: private boolean allowOthers;
061: private boolean ifExists;
062: private Connection managementDb;
063: private PreparedStatement managementDbAdd;
064: private PreparedStatement managementDbRemove;
065: private String managementPassword = "";
066: private static final Map SERVERS = Collections
067: .synchronizedMap(new HashMap());
068: private Thread listenerThread;
069: private int nextThreadId;
070:
071: /**
072: * Get the database name of the management database.
073: * The management database contains a table with active sessions (SESSIONS).
074: *
075: * @param port the TCP server port
076: * @return the database name (usually starting with mem:)
077: */
078: public static String getManagementDbName(int port) {
079: return "mem:" + Constants.MANAGEMENT_DB_PREFIX + port;
080: }
081:
082: private void initManagementDb() throws SQLException {
083: Properties prop = new Properties();
084: prop.setProperty("user", "sa");
085: prop.setProperty("password", managementPassword);
086: // avoid using the driver manager
087: Connection conn = Driver.load().connect(
088: "jdbc:h2:" + getManagementDbName(port), prop);
089: managementDb = conn;
090: Statement stat = null;
091: try {
092: stat = conn.createStatement();
093: stat
094: .execute("CREATE ALIAS IF NOT EXISTS STOP_SERVER FOR \""
095: + TcpServer.class.getName()
096: + ".stopServer\"");
097: stat
098: .execute("CREATE TABLE IF NOT EXISTS SESSIONS(ID INT PRIMARY KEY, URL VARCHAR, USER VARCHAR, CONNECTED TIMESTAMP)");
099: managementDbAdd = conn
100: .prepareStatement("INSERT INTO SESSIONS VALUES(?, ?, ?, NOW())");
101: managementDbRemove = conn
102: .prepareStatement("DELETE FROM SESSIONS WHERE ID=?");
103: } finally {
104: JdbcUtils.closeSilently(stat);
105: }
106: SERVERS.put("" + port, this );
107: }
108:
109: /**
110: * Get the list of ports of all running TCP server.
111: *
112: * @return the list of ports
113: */
114: public static int[] getAllServerPorts() {
115: Object[] servers = SERVERS.keySet().toArray();
116: int[] ports = new int[servers.length];
117: for (int i = 0; i < servers.length; i++) {
118: ports[i] = Integer.parseInt(servers[i].toString());
119: }
120: return ports;
121: }
122:
123: synchronized void addConnection(int id, String url, String user) {
124: try {
125: managementDbAdd.setInt(1, id);
126: managementDbAdd.setString(2, url);
127: managementDbAdd.setString(3, user);
128: managementDbAdd.execute();
129: } catch (SQLException e) {
130: TraceSystem.traceThrowable(e);
131: }
132: }
133:
134: synchronized void removeConnection(int id) {
135: try {
136: managementDbRemove.setInt(1, id);
137: managementDbRemove.execute();
138: } catch (SQLException e) {
139: TraceSystem.traceThrowable(e);
140: }
141: }
142:
143: private synchronized void stopManagementDb() {
144: if (managementDb != null) {
145: try {
146: managementDb.close();
147: } catch (SQLException e) {
148: TraceSystem.traceThrowable(e);
149: }
150: managementDb = null;
151: }
152: }
153:
154: public void init(String[] args) throws Exception {
155: port = DEFAULT_PORT;
156: for (int i = 0; i < args.length; i++) {
157: String a = args[i];
158: if ("-log".equals(a)) {
159: log = Boolean.valueOf(args[++i]).booleanValue();
160: } else if ("-tcpSSL".equals(a)) {
161: ssl = Boolean.valueOf(args[++i]).booleanValue();
162: } else if ("-tcpPort".equals(a)) {
163: port = MathUtils.decodeInt(args[++i]);
164: } else if ("-tcpPassword".equals(a)) {
165: managementPassword = args[++i];
166: } else if ("-baseDir".equals(a)) {
167: baseDir = args[++i];
168: } else if ("-tcpAllowOthers".equals(a)) {
169: allowOthers = Boolean.valueOf(args[++i]).booleanValue();
170: } else if ("-ifExists".equals(a)) {
171: ifExists = Boolean.valueOf(args[++i]).booleanValue();
172: }
173: }
174: org.h2.Driver.load();
175: url = (ssl ? "ssl" : "tcp") + "://"
176: + NetUtils.getLocalAddress() + ":" + port;
177: }
178:
179: public String getURL() {
180: return url;
181: }
182:
183: boolean allow(Socket socket) {
184: if (allowOthers) {
185: return true;
186: }
187: return NetUtils.isLoopbackAddress(socket);
188: }
189:
190: public synchronized void start() throws SQLException {
191: serverSocket = NetUtils.createServerSocket(port, ssl);
192: initManagementDb();
193: }
194:
195: public void listen() {
196: listenerThread = Thread.currentThread();
197: String threadName = listenerThread.getName();
198: try {
199: while (!stop) {
200: Socket s = serverSocket.accept();
201: TcpServerThread c = new TcpServerThread(s, this ,
202: nextThreadId++);
203: running.add(c);
204: Thread thread = new Thread(c);
205: thread.setName(threadName + " thread");
206: c.setThread(thread);
207: thread.start();
208: }
209: serverSocket = NetUtils.closeSilently(serverSocket);
210: } catch (Exception e) {
211: if (!stop) {
212: TraceSystem.traceThrowable(e);
213: }
214: }
215: stopManagementDb();
216: }
217:
218: public synchronized boolean isRunning() {
219: if (serverSocket == null) {
220: return false;
221: }
222: try {
223: Socket s = NetUtils.createLoopbackSocket(port, ssl);
224: s.close();
225: return true;
226: } catch (Exception e) {
227: return false;
228: }
229: }
230:
231: public void stop() {
232: // TODO server: share code between web and tcp servers
233: // need to remove the server first, otherwise the connection is broken
234: // while the server is still registered in this map
235: SERVERS.remove("" + port);
236: if (!stop) {
237: stopManagementDb();
238: stop = true;
239: if (serverSocket != null) {
240: try {
241: serverSocket.close();
242: } catch (IOException e) {
243: TraceSystem.traceThrowable(e);
244: }
245: serverSocket = null;
246: }
247: if (listenerThread != null) {
248: try {
249: listenerThread.join(1000);
250: } catch (InterruptedException e) {
251: TraceSystem.traceThrowable(e);
252: }
253: }
254: }
255: // TODO server: using a boolean 'now' argument? a timeout?
256: ArrayList list = new ArrayList(running);
257: for (int i = 0; i < list.size(); i++) {
258: TcpServerThread c = (TcpServerThread) list.get(i);
259: if (c != null) {
260: c.close();
261: try {
262: c.getThread().join(100);
263: } catch (Exception e) {
264: TraceSystem.traceThrowable(e);
265: }
266: }
267: }
268: }
269:
270: public static void stopServer(int port, String password,
271: int shutdownMode) {
272: TcpServer server = (TcpServer) SERVERS.get("" + port);
273: if (server == null) {
274: return;
275: }
276: if (!server.managementPassword.equals(password)) {
277: return;
278: }
279: if (shutdownMode == SHUTDOWN_NORMAL) {
280: server.stopManagementDb();
281: server.stop = true;
282: try {
283: Socket s = NetUtils.createLoopbackSocket(port, false);
284: s.close();
285: } catch (Exception e) {
286: // try to connect - so that accept returns
287: }
288: } else if (shutdownMode == SHUTDOWN_FORCE) {
289: server.stop();
290: }
291: }
292:
293: void remove(TcpServerThread t) {
294: running.remove(t);
295: }
296:
297: String getBaseDir() {
298: return baseDir;
299: }
300:
301: void log(String s) {
302: // TODO log: need concept for server log
303: if (log) {
304: System.out.println(s);
305: }
306: }
307:
308: void logError(Throwable e) {
309: if (log) {
310: e.printStackTrace();
311: }
312: }
313:
314: public boolean getAllowOthers() {
315: return allowOthers;
316: }
317:
318: public String getType() {
319: return "TCP";
320: }
321:
322: public String getName() {
323: return "H2 TCP Server";
324: }
325:
326: public void logInternalError(String string) {
327: if (logInternalErrors) {
328: System.out.println(string);
329: new Error(string).printStackTrace();
330: }
331: }
332:
333: public boolean getIfExists() {
334: return ifExists;
335: }
336:
337: public static synchronized void shutdown(String url,
338: String password, boolean force) throws SQLException {
339: int port = Constants.DEFAULT_SERVER_PORT;
340: int idx = url.indexOf(':', "jdbc:h2:".length());
341: if (idx >= 0) {
342: String p = url.substring(idx + 1);
343: idx = p.indexOf('/');
344: if (idx >= 0) {
345: p = p.substring(0, idx);
346: }
347: port = MathUtils.decodeInt(p);
348: }
349: String db = getManagementDbName(port);
350: try {
351: org.h2.Driver.load();
352: } catch (Throwable e) {
353: throw Message.convert(e);
354: }
355: for (int i = 0; i < 2; i++) {
356: Connection conn = null;
357: PreparedStatement prep = null;
358: try {
359: conn = DriverManager.getConnection("jdbc:h2:" + url
360: + "/" + db, "sa", password);
361: prep = conn
362: .prepareStatement("CALL STOP_SERVER(?, ?, ?)");
363: prep.setInt(1, port);
364: prep.setString(2, password);
365: prep
366: .setInt(3, force ? SHUTDOWN_FORCE
367: : SHUTDOWN_NORMAL);
368: try {
369: prep.execute();
370: } catch (SQLException e) {
371: if (force) {
372: // ignore
373: } else {
374: throw e;
375: }
376: }
377: break;
378: } catch (SQLException e) {
379: if (i == 1) {
380: throw e;
381: }
382: } finally {
383: JdbcUtils.closeSilently(prep);
384: JdbcUtils.closeSilently(conn);
385: }
386: }
387: }
388:
389: }
|