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