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

COVERAGE SUMMARY FOR SOURCE FILE [Engine.java]

nameclass, %method, %block, %line, %
Engine.java100% (1/1)100% (10/10)82%  (452/551)81%  (125.7/155)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Engine100% (1/1)100% (10/10)82%  (452/551)81%  (125.7/155)
validateUserAndPassword (boolean): void 100% (1/1)56%  (55/99)56%  (15.7/28)
close (String): void 100% (1/1)62%  (18/29)71%  (5/7)
createSessionAndValidate (ConnectionInfo): Session 100% (1/1)63%  (31/49)61%  (11/18)
checkClustering (ConnectionInfo, Database): void 100% (1/1)92%  (34/37)91%  (10/11)
openSession (ConnectionInfo, boolean, String): Session 100% (1/1)93%  (174/188)94%  (44/47)
openSession (ConnectionInfo): Session 100% (1/1)93%  (120/129)89%  (33/37)
<static initializer> 100% (1/1)100% (7/7)100% (2/2)
Engine (): void 100% (1/1)100% (7/7)100% (3/3)
createSession (ConnectionInfo): Session 100% (1/1)100% (4/4)100% (1/1)
getInstance (): Engine 100% (1/1)100% (2/2)100% (1/1)

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.engine;
7 
8import java.util.HashMap;
9 
10import org.h2.api.ErrorCode;
11import org.h2.command.CommandInterface;
12import org.h2.command.Parser;
13import org.h2.command.dml.SetTypes;
14import org.h2.message.DbException;
15import org.h2.store.FileLock;
16import org.h2.util.MathUtils;
17import org.h2.util.New;
18import org.h2.util.StringUtils;
19import org.h2.util.Utils;
20 
21/**
22 * The engine contains a map of all open databases.
23 * It is also responsible for opening and creating new databases.
24 * This is a singleton class.
25 */
26public class Engine implements SessionFactory {
27 
28    private static final Engine INSTANCE = new Engine();
29    private static final HashMap<String, Database> DATABASES = New.hashMap();
30 
31    private volatile long wrongPasswordDelay =
32            SysProperties.DELAY_WRONG_PASSWORD_MIN;
33    private boolean jmx;
34 
35    private Engine() {
36        // use getInstance()
37    }
38 
39    public static Engine getInstance() {
40        return INSTANCE;
41    }
42 
43    private Session openSession(ConnectionInfo ci, boolean ifExists,
44            String cipher) {
45        String name = ci.getName();
46        Database database;
47        ci.removeProperty("NO_UPGRADE", false);
48        boolean openNew = ci.getProperty("OPEN_NEW", false);
49        if (openNew || ci.isUnnamedInMemory()) {
50            database = null;
51        } else {
52            database = DATABASES.get(name);
53        }
54        User user = null;
55        boolean opened = false;
56        if (database == null) {
57            if (ifExists && !Database.exists(name)) {
58                throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, name);
59            }
60            database = new Database(ci, cipher);
61            opened = true;
62            if (database.getAllUsers().size() == 0) {
63                // users is the last thing we add, so if no user is around,
64                // the database is new (or not initialized correctly)
65                user = new User(database, database.allocateObjectId(),
66                        ci.getUserName(), false);
67                user.setAdmin(true);
68                user.setUserPasswordHash(ci.getUserPasswordHash());
69                database.setMasterUser(user);
70            }
71            if (!ci.isUnnamedInMemory()) {
72                DATABASES.put(name, database);
73            }
74        }
75        synchronized (database) {
76            if (opened) {
77                // start the thread when already synchronizing on the database
78                // otherwise a deadlock can occur when the writer thread
79                // opens a new database (as in recovery testing)
80                database.opened();
81            }
82            if (database.isClosing()) {
83                return null;
84            }
85            if (user == null) {
86                if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
87                    user = database.findUser(ci.getUserName());
88                    if (user != null) {
89                        if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
90                            user = null;
91                        }
92                    }
93                }
94                if (opened && (user == null || !user.isAdmin())) {
95                    // reset - because the user is not an admin, and has no
96                    // right to listen to exceptions
97                    database.setEventListener(null);
98                }
99            }
100            if (user == null) {
101                database.removeSession(null);
102                throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
103            }
104            checkClustering(ci, database);
105            Session session = database.createSession(user);
106            if (ci.getProperty("JMX", false)) {
107                try {
108                    Utils.callStaticMethod(
109                            "org.h2.jmx.DatabaseInfo.registerMBean", ci, database);
110                } catch (Exception e) {
111                    database.removeSession(session);
112                    throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX");
113                }
114                jmx = true;
115            }
116            return session;
117        }
118    }
119 
120    /**
121     * Open a database connection with the given connection information.
122     *
123     * @param ci the connection information
124     * @return the session
125     */
126    @Override
127    public Session createSession(ConnectionInfo ci) {
128        return INSTANCE.createSessionAndValidate(ci);
129    }
130 
131    private Session createSessionAndValidate(ConnectionInfo ci) {
132        try {
133            ConnectionInfo backup = null;
134            String lockMethodName = ci.getProperty("FILE_LOCK", null);
135            int fileLockMethod = FileLock.getFileLockMethod(lockMethodName);
136            if (fileLockMethod == FileLock.LOCK_SERIALIZED) {
137                // In serialized mode, database instance sharing is not possible
138                ci.setProperty("OPEN_NEW", "TRUE");
139                try {
140                    backup = ci.clone();
141                } catch (CloneNotSupportedException e) {
142                    throw DbException.convert(e);
143                }
144            }
145            Session session = openSession(ci);
146            validateUserAndPassword(true);
147            if (backup != null) {
148                session.setConnectionInfo(backup);
149            }
150            return session;
151        } catch (DbException e) {
152            if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) {
153                validateUserAndPassword(false);
154            }
155            throw e;
156        }
157    }
158 
159    private synchronized Session openSession(ConnectionInfo ci) {
160        boolean ifExists = ci.removeProperty("IFEXISTS", false);
161        boolean ignoreUnknownSetting = ci.removeProperty(
162                "IGNORE_UNKNOWN_SETTINGS", false);
163        String cipher = ci.removeProperty("CIPHER", null);
164        String init = ci.removeProperty("INIT", null);
165        Session session;
166        for (int i = 0;; i++) {
167            session = openSession(ci, ifExists, cipher);
168            if (session != null) {
169                break;
170            }
171            // we found a database that is currently closing
172            // wait a bit to avoid a busy loop (the method is synchronized)
173            if (i > 60 * 1000) {
174                // retry at most 1 minute
175                throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1,
176                        "Waited for database closing longer than 1 minute");
177            }
178            try {
179                Thread.sleep(1);
180            } catch (InterruptedException e) {
181                // ignore
182            }
183        }
184        session.setAllowLiterals(true);
185        DbSettings defaultSettings = DbSettings.getDefaultSettings();
186        for (String setting : ci.getKeys()) {
187            if (defaultSettings.containsKey(setting)) {
188                // database setting are only used when opening the database
189                continue;
190            }
191            String value = ci.getProperty(setting);
192            try {
193                CommandInterface command = session.prepareCommand(
194                        "SET " + Parser.quoteIdentifier(setting) + " " + value,
195                        Integer.MAX_VALUE);
196                command.executeUpdate();
197            } catch (DbException e) {
198                if (!ignoreUnknownSetting) {
199                    session.close();
200                    throw e;
201                }
202            }
203        }
204        if (init != null) {
205            try {
206                CommandInterface command = session.prepareCommand(init,
207                        Integer.MAX_VALUE);
208                command.executeUpdate();
209            } catch (DbException e) {
210                if (!ignoreUnknownSetting) {
211                    session.close();
212                    throw e;
213                }
214            }
215        }
216        session.setAllowLiterals(false);
217        session.commit(true);
218        return session;
219    }
220 
221    private static void checkClustering(ConnectionInfo ci, Database database) {
222        String clusterSession = ci.getProperty(SetTypes.CLUSTER, null);
223        if (Constants.CLUSTERING_DISABLED.equals(clusterSession)) {
224            // in this case, no checking is made
225            // (so that a connection can be made to disable/change clustering)
226            return;
227        }
228        String clusterDb = database.getCluster();
229        if (!Constants.CLUSTERING_DISABLED.equals(clusterDb)) {
230            if (!Constants.CLUSTERING_ENABLED.equals(clusterSession)) {
231                if (!StringUtils.equals(clusterSession, clusterDb)) {
232                    if (clusterDb.equals(Constants.CLUSTERING_DISABLED)) {
233                        throw DbException.get(
234                                ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_ALONE);
235                    }
236                    throw DbException.get(
237                            ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1,
238                            clusterDb);
239                }
240            }
241        }
242    }
243 
244    /**
245     * Called after a database has been closed, to remove the object from the
246     * list of open databases.
247     *
248     * @param name the database name
249     */
250    void close(String name) {
251        if (jmx) {
252            try {
253                Utils.callStaticMethod("org.h2.jmx.DatabaseInfo.unregisterMBean", name);
254            } catch (Exception e) {
255                throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX");
256            }
257        }
258        DATABASES.remove(name);
259    }
260 
261    /**
262     * This method is called after validating user name and password. If user
263     * name and password were correct, the sleep time is reset, otherwise this
264     * method waits some time (to make brute force / rainbow table attacks
265     * harder) and then throws a 'wrong user or password' exception. The delay
266     * is a bit randomized to protect against timing attacks. Also the delay
267     * doubles after each unsuccessful logins, to make brute force attacks
268     * harder.
269     *
270     * There is only one exception message both for wrong user and for
271     * wrong password, to make it harder to get the list of user names. This
272     * method must only be called from one place, so it is not possible from the
273     * stack trace to see if the user name was wrong or the password.
274     *
275     * @param correct if the user name or the password was correct
276     * @throws DbException the exception 'wrong user or password'
277     */
278    private void validateUserAndPassword(boolean correct) {
279        int min = SysProperties.DELAY_WRONG_PASSWORD_MIN;
280        if (correct) {
281            long delay = wrongPasswordDelay;
282            if (delay > min && delay > 0) {
283                // the first correct password must be blocked,
284                // otherwise parallel attacks are possible
285                synchronized (INSTANCE) {
286                    // delay up to the last delay
287                    // an attacker can't know how long it will be
288                    delay = MathUtils.secureRandomInt((int) delay);
289                    try {
290                        Thread.sleep(delay);
291                    } catch (InterruptedException e) {
292                        // ignore
293                    }
294                    wrongPasswordDelay = min;
295                }
296            }
297        } else {
298            // this method is not synchronized on the Engine, so that
299            // regular successful attempts are not blocked
300            synchronized (INSTANCE) {
301                long delay = wrongPasswordDelay;
302                int max = SysProperties.DELAY_WRONG_PASSWORD_MAX;
303                if (max <= 0) {
304                    max = Integer.MAX_VALUE;
305                }
306                wrongPasswordDelay += wrongPasswordDelay;
307                if (wrongPasswordDelay > max || wrongPasswordDelay < 0) {
308                    wrongPasswordDelay = max;
309                }
310                if (min > 0) {
311                    // a bit more to protect against timing attacks
312                    delay += Math.abs(MathUtils.secureRandomLong() % 100);
313                    try {
314                        Thread.sleep(delay);
315                    } catch (InterruptedException e) {
316                        // ignore
317                    }
318                }
319                throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD);
320            }
321        }
322    }
323 
324}

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