File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\server\TcpServer.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.server;
8
9 import java.io.IOException;
10 import java.net.ServerSocket;
11 import java.net.Socket;
12 import java.net.UnknownHostException;
13 import java.sql.Connection;
14 import java.sql.DriverManager;
15 import java.sql.PreparedStatement;
16 import java.sql.SQLException;
17 import java.sql.Statement;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Properties;
23 import java.util.Set;
24 import org.h2.Driver;
25 import org.h2.constant.ErrorCode;
26 import org.h2.engine.Constants;
27 import org.h2.message.DbException;
28 import org.h2.message.TraceSystem;
29 import org.h2.util.JdbcUtils;
30 import org.h2.util.NetUtils;
31 import org.h2.util.New;
32 import org.h2.util.StringUtils;
33 import org.h2.util.Tool;
34
35 /**
36 * The TCP server implements the native H2 database server protocol.
37 * It supports multiple client connections to multiple databases
38 * (many to many). The same database may be opened by multiple clients.
39 * Also supported is the mixed mode: opening databases in embedded mode,
40 * and at the same time start a TCP server to allow clients to connect to
41 * the same database over the network.
42 */
43 public class TcpServer implements Service {
44
45 private static final int SHUTDOWN_NORMAL = 0;
46 private static final int SHUTDOWN_FORCE = 1;
47
48 /**
49 * The name of the in-memory management database used by the TCP server
50 * to keep the active sessions.
51 */
52 private static final String MANAGEMENT_DB_PREFIX = "management_db_";
53
54 private static final Map<Integer, TcpServer> SERVERS = Collections.synchronizedMap(new HashMap<Integer, TcpServer>());
55
56 private int port;
57 private boolean portIsSet;
58 private boolean trace;
59 private boolean ssl;
60 private boolean stop;
61 private ShutdownHandler shutdownHandler;
62 private ServerSocket serverSocket;
63 private final Set<TcpServerThread> running = Collections.synchronizedSet(new HashSet<TcpServerThread>());
64 private String baseDir;
65 private boolean allowOthers;
66 private boolean isDaemon;
67 private boolean ifExists;
68 private Connection managementDb;
69 private PreparedStatement managementDbAdd;
70 private PreparedStatement managementDbRemove;
71 private String managementPassword = "";
72 private Thread listenerThread;
73 private int nextThreadId;
74 private String key, keyDatabase;
75
76 /**
77 * Get the database name of the management database.
78 * The management database contains a table with active sessions (SESSIONS).
79 *
80 * @param port the TCP server port
81 * @return the database name (usually starting with mem:)
82 */
83 public static String getManagementDbName(int port) {
84 return "mem:" + MANAGEMENT_DB_PREFIX + port;
85 }
86
87 private void initManagementDb() throws SQLException {
88 Properties prop = new Properties();
89 prop.setProperty("user", "");
90 prop.setProperty("password", managementPassword);
91 // avoid using the driver manager
92 Connection conn = Driver.load().connect("jdbc:h2:" + getManagementDbName(port), prop);
93 managementDb = conn;
94 Statement stat = null;
95 try {
96 stat = conn.createStatement();
97 stat.execute("CREATE ALIAS IF NOT EXISTS STOP_SERVER FOR \"" + TcpServer.class.getName() + ".stopServer\"");
98 stat.execute("CREATE TABLE IF NOT EXISTS SESSIONS(ID INT PRIMARY KEY, URL VARCHAR, USER VARCHAR, CONNECTED TIMESTAMP)");
99 managementDbAdd = conn.prepareStatement("INSERT INTO SESSIONS VALUES(?, ?, ?, NOW())");
100 managementDbRemove = conn.prepareStatement("DELETE FROM SESSIONS WHERE ID=?");
101 } finally {
102 JdbcUtils.closeSilently(stat);
103 }
104 SERVERS.put(port, this);
105 }
106
107 /**
108 * Shut down this server.
109 */
110 void shutdown() {
111 if (shutdownHandler != null) {
112 shutdownHandler.shutdown();
113 }
114 }
115
116 public void setShutdownHandler(ShutdownHandler shutdownHandler) {
117 this.shutdownHandler = shutdownHandler;
118 }
119
120 /**
121 * Add a connection to the management database.
122 *
123 * @param id the connection id
124 * @param url the database URL
125 * @param user the user name
126 */
127 synchronized void addConnection(int id, String url, String user) {
128 try {
129 managementDbAdd.setInt(1, id);
130 managementDbAdd.setString(2, url);
131 managementDbAdd.setString(3, user);
132 managementDbAdd.execute();
133 } catch (SQLException e) {
134 TraceSystem.traceThrowable(e);
135 }
136 }
137
138 /**
139 * Remove a connection from the management database.
140 *
141 * @param id the connection id
142 */
143 synchronized void removeConnection(int id) {
144 try {
145 managementDbRemove.setInt(1, id);
146 managementDbRemove.execute();
147 } catch (SQLException e) {
148 TraceSystem.traceThrowable(e);
149 }
150 }
151
152 private synchronized void stopManagementDb() {
153 if (managementDb != null) {
154 try {
155 managementDb.close();
156 } catch (SQLException e) {
157 TraceSystem.traceThrowable(e);
158 }
159 managementDb = null;
160 }
161 }
162
163 public void init(String... args) {
164 port = Constants.DEFAULT_TCP_PORT;
165 for (int i = 0; args != null && i < args.length; i++) {
166 String a = args[i];
167 if (Tool.isOption(a, "-trace")) {
168 trace = true;
169 } else if (Tool.isOption(a, "-tcpSSL")) {
170 ssl = true;
171 } else if (Tool.isOption(a, "-tcpPort")) {
172 port = Integer.decode(args[++i]);
173 portIsSet = true;
174 } else if (Tool.isOption(a, "-tcpPassword")) {
175 managementPassword = args[++i];
176 } else if (Tool.isOption(a, "-baseDir")) {
177 baseDir = args[++i];
178 } else if (Tool.isOption(a, "-key")) {
179 key = args[++i];
180 keyDatabase = args[++i];
181 } else if (Tool.isOption(a, "-tcpAllowOthers")) {
182 allowOthers = true;
183 } else if (Tool.isOption(a, "-tcpDaemon")) {
184 isDaemon = true;
185 } else if (Tool.isOption(a, "-ifExists")) {
186 ifExists = true;
187 }
188 }
189 org.h2.Driver.load();
190 }
191
192 public String getURL() {
193 return (ssl ? "ssl" : "tcp") + "://" + NetUtils.getLocalAddress() + ":" + port;
194 }
195
196 public int getPort() {
197 return port;
198 }
199
200 /**
201 * Check if this socket may connect to this server. Remote connections are
202 * not allowed if the flag allowOthers is set.
203 *
204 * @param socket the socket
205 * @return true if this client may connect
206 */
207 boolean allow(Socket socket) {
208 if (allowOthers) {
209 return true;
210 }
211 try {
212 return NetUtils.isLocalAddress(socket);
213 } catch (UnknownHostException e) {
214 traceError(e);
215 return false;
216 }
217 }
218
219 public synchronized void start() throws SQLException {
220 stop = false;
221 try {
222 serverSocket = NetUtils.createServerSocket(port, ssl);
223 } catch (DbException e) {
224 if (!portIsSet) {
225 serverSocket = NetUtils.createServerSocket(0, ssl);
226 } else {
227 throw e;
228 }
229 }
230 port = serverSocket.getLocalPort();
231 initManagementDb();
232 }
233
234 public void listen() {
235 listenerThread = Thread.currentThread();
236 String threadName = listenerThread.getName();
237 try {
238 while (!stop) {
239 Socket s = serverSocket.accept();
240 TcpServerThread c = new TcpServerThread(s, this, nextThreadId++);
241 running.add(c);
242 Thread thread = new Thread(c, threadName + " thread");
243 thread.setDaemon(isDaemon);
244 c.setThread(thread);
245 thread.start();
246 }
247 serverSocket = NetUtils.closeSilently(serverSocket);
248 } catch (Exception e) {
249 if (!stop) {
250 TraceSystem.traceThrowable(e);
251 }
252 }
253 stopManagementDb();
254 }
255
256 public synchronized boolean isRunning(boolean traceError) {
257 if (serverSocket == null) {
258 return false;
259 }
260 try {
261 Socket s = NetUtils.createLoopbackSocket(port, ssl);
262 s.close();
263 return true;
264 } catch (Exception e) {
265 if (traceError) {
266 traceError(e);
267 }
268 return false;
269 }
270 }
271
272 public void stop() {
273 // TODO server: share code between web and tcp servers
274 // need to remove the server first, otherwise the connection is broken
275 // while the server is still registered in this map
276 SERVERS.remove(port);
277 if (!stop) {
278 stopManagementDb();
279 stop = true;
280 if (serverSocket != null) {
281 try {
282 serverSocket.close();
283 } catch (IOException e) {
284 TraceSystem.traceThrowable(e);
285 } catch (NullPointerException e) {
286 // ignore
287 }
288 serverSocket = null;
289 }
290 if (listenerThread != null) {
291 try {
292 listenerThread.join(1000);
293 } catch (InterruptedException e) {
294 TraceSystem.traceThrowable(e);
295 }
296 }
297 }
298 // TODO server: using a boolean 'now' argument? a timeout?
299 for (TcpServerThread c : New.arrayList(running)) {
300 if (c != null) {
301 c.close();
302 try {
303 c.getThread().join(100);
304 } catch (Exception e) {
305 TraceSystem.traceThrowable(e);
306 }
307 }
308 }
309 }
310
311 /**
312 * Stop a running server. This method is called via reflection from the
313 * STOP_SERVER function.
314 *
315 * @param port the port where the server runs, or 0 for all running servers
316 * @param password the password (or null)
317 * @param shutdownMode the shutdown mode, SHUTDOWN_NORMAL or SHUTDOWN_FORCE.
318 */
319 public static void stopServer(int port, String password, int shutdownMode) {
320 if (port == 0) {
321 for (int p : SERVERS.keySet().toArray(new Integer[0])) {
322 if (p != 0) {
323 stopServer(p, password, shutdownMode);
324 }
325 }
326 return;
327 }
328 TcpServer server = SERVERS.get(port);
329 if (server == null) {
330 return;
331 }
332 if (!server.managementPassword.equals(password)) {
333 return;
334 }
335 if (shutdownMode == SHUTDOWN_NORMAL) {
336 server.stopManagementDb();
337 server.stop = true;
338 try {
339 Socket s = NetUtils.createLoopbackSocket(port, false);
340 s.close();
341 } catch (Exception e) {
342 // try to connect - so that accept returns
343 }
344 } else if (shutdownMode == SHUTDOWN_FORCE) {
345 server.stop();
346 }
347 server.shutdown();
348 }
349
350 /**
351 * Remove a thread from the list.
352 *
353 * @param t the thread to remove
354 */
355 void remove(TcpServerThread t) {
356 running.remove(t);
357 }
358
359 /**
360 * Get the configured base directory.
361 *
362 * @return the base directory
363 */
364 String getBaseDir() {
365 return baseDir;
366 }
367
368 /**
369 * Print a message if the trace flag is enabled.
370 *
371 * @param s the message
372 */
373 void trace(String s) {
374 if (trace) {
375 System.out.println(s);
376 }
377 }
378 /**
379 * Print a stack trace if the trace flag is enabled.
380 *
381 * @param e the exception
382 */
383 void traceError(Throwable e) {
384 if (trace) {
385 e.printStackTrace();
386 }
387 }
388
389 public boolean getAllowOthers() {
390 return allowOthers;
391 }
392
393 public String getType() {
394 return "TCP";
395 }
396
397 public String getName() {
398 return "H2 TCP Server";
399 }
400
401 boolean getIfExists() {
402 return ifExists;
403 }
404
405 /**
406 * Stop the TCP server with the given URL.
407 *
408 * @param url the database URL
409 * @param password the password
410 * @param force if the server should be stopped immediately
411 * @param all whether all TCP servers that are running in the JVM should be
412 * stopped
413 */
414 public static synchronized void shutdown(String url, String password, boolean force, boolean all) throws SQLException {
415 try {
416 int port = Constants.DEFAULT_TCP_PORT;
417 int idx = url.lastIndexOf(':');
418 if (idx >= 0) {
419 String p = url.substring(idx + 1);
420 if (StringUtils.isNumber(p)) {
421 port = Integer.decode(p);
422 }
423 }
424 String db = getManagementDbName(port);
425 try {
426 org.h2.Driver.load();
427 } catch (Throwable e) {
428 throw DbException.convert(e);
429 }
430 for (int i = 0; i < 2; i++) {
431 Connection conn = null;
432 PreparedStatement prep = null;
433 try {
434 conn = DriverManager.getConnection("jdbc:h2:" + url + "/" + db, "", password);
435 prep = conn.prepareStatement("CALL STOP_SERVER(?, ?, ?)");
436 prep.setInt(1, all ? 0 : port);
437 prep.setString(2, password);
438 prep.setInt(3, force ? SHUTDOWN_FORCE : SHUTDOWN_NORMAL);
439 try {
440 prep.execute();
441 } catch (SQLException e) {
442 if (force) {
443 // ignore
444 } else {
445 if (e.getErrorCode() != ErrorCode.CONNECTION_BROKEN_1) {
446 throw e;
447 }
448 }
449 }
450 break;
451 } catch (SQLException e) {
452 if (i == 1) {
453 throw e;
454 }
455 } finally {
456 JdbcUtils.closeSilently(prep);
457 JdbcUtils.closeSilently(conn);
458 }
459 }
460 } catch (Exception e) {
461 throw DbException.toSQLException(e);
462 }
463 }
464
465 /**
466 * Cancel a running statement.
467 *
468 * @param sessionId the session id
469 * @param statementId the statement id
470 */
471 void cancelStatement(String sessionId, int statementId) {
472 for (TcpServerThread c : New.arrayList(running)) {
473 if (c != null) {
474 c.cancelStatement(sessionId, statementId);
475 }
476 }
477 }
478
479 /**
480 * If no key is set, return the original database name. If a key is set,
481 * check if the key matches. If yes, return the correct database name. If
482 * not, throw an exception.
483 *
484 * @param db the key to test (or database name if no key is used)
485 * @return the database name
486 * @throws DbException if a key is set but doesn't match
487 */
488 public String checkKeyAndGetDatabaseName(String db) {
489 if (key == null) {
490 return db;
491 }
492 if (key.equals(db)) {
493 return keyDatabase;
494 }
495 throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
496 }
497
498 public boolean isDaemon() {
499 return isDaemon;
500 }
501
502 }
503