EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.server]

COVERAGE SUMMARY FOR SOURCE FILE [TcpServer.java]

nameclass, %method, %block, %line, %
TcpServer.java100% (1/1)100% (30/30)89%  (697/780)87%  (191.2/221)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class TcpServer100% (1/1)100% (30/30)89%  (697/780)87%  (191.2/221)
stopServer (int, String, int): void 100% (1/1)53%  (37/70)66%  (13.8/21)
allow (Socket): boolean 100% (1/1)57%  (8/14)50%  (3/6)
trace (String): void 100% (1/1)57%  (4/7)67%  (2/3)
traceError (Throwable): void 100% (1/1)67%  (4/6)67%  (2/3)
stopManagementDb (): void 100% (1/1)79%  (11/14)71%  (5/7)
stop (): void 100% (1/1)83%  (55/66)70%  (17.5/25)
isRunning (boolean): boolean 100% (1/1)87%  (20/23)89%  (8/9)
addConnection (int, String, String): void 100% (1/1)88%  (21/24)75%  (6/8)
shutdown (String, String, boolean, boolean): void 100% (1/1)94%  (117/125)91%  (32/35)
initManagementDb (): void 100% (1/1)94%  (76/81)100% (14.9/15)
init (String []): void 100% (1/1)96%  (110/114)96%  (24/25)
listen (): void 100% (1/1)97%  (68/70)94%  (17/18)
<static initializer> 100% (1/1)100% (6/6)100% (1/1)
TcpServer (): void 100% (1/1)100% (12/12)100% (3/3)
cancelStatement (String, int): void 100% (1/1)100% (20/20)100% (5/5)
checkKeyAndGetDatabaseName (String): String 100% (1/1)100% (16/16)100% (5/5)
getAllowOthers (): boolean 100% (1/1)100% (3/3)100% (1/1)
getBaseDir (): String 100% (1/1)100% (3/3)100% (1/1)
getIfExists (): boolean 100% (1/1)100% (3/3)100% (1/1)
getManagementDbName (int): String 100% (1/1)100% (9/9)100% (1/1)
getName (): String 100% (1/1)100% (2/2)100% (1/1)
getPort (): int 100% (1/1)100% (3/3)100% (1/1)
getType (): String 100% (1/1)100% (2/2)100% (1/1)
getURL (): String 100% (1/1)100% (21/21)100% (1/1)
isDaemon (): boolean 100% (1/1)100% (3/3)100% (1/1)
remove (TcpServerThread): void 100% (1/1)100% (6/6)100% (2/2)
removeConnection (int): void 100% (1/1)100% (14/14)100% (6/6)
setShutdownHandler (ShutdownHandler): void 100% (1/1)100% (4/4)100% (2/2)
shutdown (): void 100% (1/1)100% (7/7)100% (3/3)
start (): void 100% (1/1)100% (32/32)100% (10/10)

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

[all classes][org.h2.server]
EMMA 2.0.5312 (C) Vladimir Roubtsov