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.engine;
007:
008: import java.sql.SQLException;
009: import java.util.HashMap;
010:
011: import org.h2.command.CommandInterface;
012: import org.h2.command.Parser;
013: import org.h2.command.dml.SetTypes;
014: import org.h2.constant.ErrorCode;
015: import org.h2.message.Message;
016: import org.h2.message.Trace;
017: import org.h2.util.StringUtils;
018:
019: /**
020: * The engine contains a map of all open databases.
021: * It is also responsible for opening and creating new databases.
022: * This is a singleton class.
023: */
024: public class Engine {
025: // TODO use a 'engine'/'master' database to allow shut down the server,
026: // view & kill sessions and so on
027:
028: private static final Engine INSTANCE = new Engine();
029: private final HashMap databases = new HashMap();
030:
031: private Engine() {
032: // don't allow others to instantiate
033: }
034:
035: public static Engine getInstance() {
036: return INSTANCE;
037: }
038:
039: private Session openSession(ConnectionInfo ci, boolean ifExists,
040: String cipher) throws SQLException {
041: // may not remove properties here, otherwise they are lost
042: // if it is required to call it twice
043: String name = ci.getName();
044: Database database;
045: if (ci.isUnnamed()) {
046: database = null;
047: } else {
048: database = (Database) databases.get(name);
049: }
050: User user = null;
051: boolean opened = false;
052: if (database == null) {
053: if (ifExists && !Database.exists(name)) {
054: throw Message.getSQLException(
055: ErrorCode.DATABASE_NOT_FOUND_1, name);
056: }
057: database = new Database(name, ci, cipher);
058: opened = true;
059: if (database.getAllUsers().size() == 0) {
060: // users is the last thing we add, so if no user is around,
061: // the database is not initialized correctly
062: user = new User(database, database.allocateObjectId(
063: false, true), ci.getUserName(), false);
064: user.setAdmin(true);
065: user.setUserPasswordHash(ci.getUserPasswordHash());
066: database.setMasterUser(user);
067: }
068: if (!ci.isUnnamed()) {
069: databases.put(name, database);
070: }
071: database.opened();
072: }
073: synchronized (database) {
074: if (database.isClosing()) {
075: return null;
076: }
077: if (user == null) {
078: try {
079: database.checkFilePasswordHash(cipher, ci
080: .getFilePasswordHash());
081: // create the exception here so it is not possible from the stack trace
082: // to see if the user name was wrong or the password
083: SQLException wrongUserOrPassword = Message
084: .getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
085: user = database.getUser(ci.getUserName(),
086: wrongUserOrPassword);
087: user.checkUserPasswordHash(
088: ci.getUserPasswordHash(),
089: wrongUserOrPassword);
090: if (opened && !user.getAdmin()) {
091: // reset - because the user is not an admin, and has no
092: // right to listen to exceptions
093: database.setEventListener(null);
094: }
095: } catch (SQLException e) {
096: database.removeSession(null);
097: throw e;
098: }
099: }
100: checkClustering(ci, database);
101: Session session = database.createSession(user);
102: return session;
103: }
104: }
105:
106: public synchronized Session getSession(ConnectionInfo ci)
107: throws SQLException {
108: boolean ifExists = ci.removeProperty("IFEXISTS", false);
109: boolean ignoreUnknownSetting = ci.removeProperty(
110: "IGNORE_UNKNOWN_SETTINGS", false);
111: String cipher = ci.removeProperty("CIPHER", null);
112: Session session;
113: while (true) {
114: session = openSession(ci, ifExists, cipher);
115: if (session != null) {
116: break;
117: }
118: // we found a database that is currently closing
119: // wait a bit to avoid a busy loop (the method is synchronized)
120: try {
121: Thread.sleep(1);
122: } catch (InterruptedException e) {
123: // ignore
124: }
125: }
126: String[] keys = ci.getKeys();
127: session.setAllowLiterals(true);
128: for (int i = 0; i < keys.length; i++) {
129: String setting = keys[i];
130: String value = ci.getProperty(setting);
131: try {
132: CommandInterface command = session.prepareCommand(
133: "SET " + Parser.quoteIdentifier(setting) + " "
134: + value, Integer.MAX_VALUE);
135: command.executeUpdate();
136: } catch (SQLException e) {
137: if (!ignoreUnknownSetting) {
138: session.close();
139: throw e;
140: }
141: }
142: }
143: session.setAllowLiterals(false);
144: session.commit(true);
145: session.getDatabase().getTrace(Trace.SESSION).info(
146: "connected #" + session.getId());
147: return session;
148: }
149:
150: private void checkClustering(ConnectionInfo ci, Database database)
151: throws SQLException {
152: String clusterSession = ci.getProperty(SetTypes.CLUSTER, null);
153: if (Constants.CLUSTERING_DISABLED.equals(clusterSession)) {
154: // in this case, no checking is made
155: // (so that a connection can be made to disable/change clustering)
156: return;
157: }
158: String clusterDb = database.getCluster();
159: if (!Constants.CLUSTERING_DISABLED.equals(clusterDb)) {
160: if (!StringUtils.equals(clusterSession, clusterDb)) {
161: if (clusterDb.equals(Constants.CLUSTERING_DISABLED)) {
162: throw Message
163: .getSQLException(ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_ALONE);
164: } else {
165: throw Message
166: .getSQLException(
167: ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1,
168: clusterDb);
169: }
170: }
171: }
172: }
173:
174: public void close(String name) {
175: databases.remove(name);
176: }
177:
178: }
|