001: /**
002: * com.mckoi.database.jdbcserver.JDBCDatabaseInterface 16 Aug 2000
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database.jdbcserver;
024:
025: import com.mckoi.database.*;
026: import com.mckoi.database.jdbc.*;
027: import com.mckoi.util.StringUtil;
028: import com.mckoi.debug.*;
029:
030: import java.sql.SQLException;
031:
032: /**
033: * An implementation of jdbc.DatabaseInterface on the server-side.
034: * <p>
035: * This receives database commands and dispatches them to the database
036: * system. This assumes that all calls to the methods here are in a
037: * UserWorkerThread thread.
038: * <p>
039: * NOTE: Currently, the client/server use of this object isn't multi-threaded,
040: * however the local connection could be. Therefore, this object has been
041: * made multi-thread safe.
042: *
043: * @author Tobias Downer
044: */
045:
046: public class JDBCDatabaseInterface extends
047: AbstractJDBCDatabaseInterface {
048:
049: /**
050: * Set this to true if command logging is enabled.
051: */
052: private static final boolean COMMAND_LOGGING = true;
053:
054: /**
055: * The unique host name denoting the client that's connected.
056: */
057: private String host_name;
058:
059: /**
060: * Sets up the processor.
061: */
062: public JDBCDatabaseInterface(Database database, String host_name) {
063: super (database);
064: this .host_name = host_name;
065: }
066:
067: /**
068: * Tries to authenticate the username and password against the given
069: * database. Returns true if we are successful. If successful, alters the
070: * state of this object to reflect the fact the user has logged in.
071: */
072: private boolean authenticate(Database database,
073: String default_schema, String username, String password,
074: final DatabaseCallBack database_call_back) {
075:
076: // If the 'user' variable is null, no one is currently logged in to this
077: // connection.
078:
079: if (getUser() == null) {
080:
081: if (COMMAND_LOGGING && database.getSystem().logQueries()) {
082: // Output the instruction to the commands log.
083: StringBuffer log_str = new StringBuffer();
084: log_str.append("[JDBC] [");
085: log_str.append(username);
086: log_str.append("] ");
087: log_str.append('[');
088: log_str.append(host_name);
089: log_str.append("] ");
090: log_str.append("Log in.\n");
091: database.getCommandsLog().log(new String(log_str));
092: }
093:
094: // Write debug message,
095: if (Debug().isInterestedIn(Lvl.INFORMATION)) {
096: Debug().write(Lvl.INFORMATION, this ,
097: "Authenticate User: " + username);
098: }
099:
100: // Create a UserCallBack class.
101: DatabaseConnection.CallBack call_back = new DatabaseConnection.CallBack() {
102: public void triggerNotify(String trigger_name,
103: int trigger_event, String trigger_source,
104: int fire_count) {
105: StringBuffer message = new StringBuffer();
106: message.append(trigger_name);
107: message.append(' ');
108: message.append(trigger_source);
109: message.append(' ');
110: message.append(fire_count);
111:
112: database_call_back.databaseEvent(99, new String(
113: message));
114: }
115: };
116:
117: // Try to create a User object.
118: User this _user = database.authenticateUser(username,
119: password, host_name);
120: DatabaseConnection database_connection = null;
121:
122: // If successful, ask the engine for a DatabaseConnection object.
123: if (this _user != null) {
124: database_connection = database.createNewConnection(
125: this _user, call_back);
126:
127: // Put the connection in exclusive mode
128: LockingMechanism locker = database_connection
129: .getLockingMechanism();
130: locker.setMode(LockingMechanism.EXCLUSIVE_MODE);
131: try {
132:
133: // By default, JDBC connections are auto-commit
134: database_connection.setAutoCommit(true);
135:
136: // Set the default schema for this connection if it exists
137: if (database_connection
138: .schemaExists(default_schema)) {
139: database_connection
140: .setDefaultSchema(default_schema);
141: } else {
142: Debug().write(
143: Lvl.WARNING,
144: this ,
145: "Couldn't change to '" + default_schema
146: + "' schema.");
147: // If we can't change to the schema then change to the APP schema
148: database_connection.setDefaultSchema("APP");
149: }
150:
151: } finally {
152: try {
153: // Make sure we commit the connection.
154: database_connection.commit();
155: } catch (TransactionException e) {
156: // Just issue a warning...
157: Debug().writeException(Lvl.WARNING, e);
158: } finally {
159: // Guarentee that we unluck from EXCLUSIVE
160: locker
161: .finishMode(LockingMechanism.EXCLUSIVE_MODE);
162: }
163: }
164:
165: }
166:
167: // If we have a user object, then init the object,
168: if (this _user != null) {
169: init(this _user, database_connection);
170: return true;
171: } else {
172: // Otherwise, return false.
173: return false;
174: }
175:
176: } else {
177: throw new RuntimeException(
178: "Attempt to authenticate user twice");
179: }
180:
181: }
182:
183: // ---------- Implemented from DatabaseInterface ----------
184:
185: public boolean login(String default_schema, String username,
186: String password, DatabaseCallBack database_call_back)
187: throws SQLException {
188:
189: Database database = getDatabase();
190:
191: boolean b = authenticate(database, default_schema, username,
192: password, database_call_back);
193: return b;
194: }
195:
196: public QueryResponse execQuery(SQLQuery query) throws SQLException {
197:
198: // Check the interface isn't disposed (connection was closed).
199: checkNotDisposed();
200:
201: User user = getUser();
202: DatabaseConnection database_connection = getDatabaseConnection();
203:
204: // Log this query if query logging is enabled
205: if (COMMAND_LOGGING && getDatabase().getSystem().logQueries()) {
206: // Output the instruction to the commands log.
207: StringBuffer log_str = new StringBuffer();
208: log_str.append("[JDBC] [");
209: log_str.append(user.getUserName());
210: log_str.append("] ");
211: log_str.append('[');
212: log_str.append(host_name);
213: log_str.append("] ");
214: log_str.append("Query: ");
215: log_str.append(query.getQuery());
216: log_str.append('\n');
217: user.getDatabase().getCommandsLog()
218: .log(new String(log_str));
219: }
220:
221: // Write debug message (INFORMATION level)
222: if (Debug().isInterestedIn(Lvl.INFORMATION)) {
223: Debug().write(
224: Lvl.INFORMATION,
225: this ,
226: "Query From User: " + user.getUserName() + "@"
227: + host_name);
228: Debug().write(Lvl.INFORMATION, this ,
229: "Query: " + query.getQuery().trim());
230: }
231:
232: // Get the locking mechanism.
233: LockingMechanism locker = database_connection
234: .getLockingMechanism();
235: int lock_mode = -1;
236: QueryResponse response = null;
237: try {
238: try {
239:
240: // For simplicity - all database locking is now exclusive inside
241: // a transaction. This means it is not possible to execute
242: // queries concurrently inside a transaction. However, we are
243: // still able to execute queries concurrently from different
244: // connections.
245: //
246: // It's debatable whether we even need to perform this lock anymore
247: // because we could change the contract of this method so that
248: // it is not thread safe. This would require that the callee ensures
249: // more than one thread can not execute queries on the connection.
250: lock_mode = LockingMechanism.EXCLUSIVE_MODE;
251: locker.setMode(lock_mode);
252:
253: // Execute the query (behaviour for this comes from super).
254: response = super .execQuery(query);
255:
256: // Return the result.
257: return response;
258:
259: } finally {
260: try {
261: // This is executed no matter what happens. Very important we
262: // unlock the tables.
263: if (lock_mode != -1) {
264: locker.finishMode(lock_mode);
265: }
266: } catch (Throwable e) {
267: // If this throws an exception, we should output it to the debug
268: // log and screen.
269: e.printStackTrace(System.err);
270: Debug().write(Lvl.ERROR, this ,
271: "Exception finishing locks");
272: Debug().writeException(e);
273: // Note, we can't throw an error here because we may already be in
274: // an exception that happened in the above 'try' block.
275: }
276: }
277:
278: } finally {
279: // This always happens after tables are unlocked.
280: // Also guarenteed to happen even if something fails.
281:
282: // If we are in auto-commit mode then commit the query here.
283: // Do we auto-commit?
284: if (database_connection.getAutoCommit()) {
285: // Yes, so grab an exclusive lock and auto-commit.
286: try {
287: // Lock into exclusive mode.
288: locker.setMode(LockingMechanism.EXCLUSIVE_MODE);
289: // If an error occured then roll-back
290: if (response == null) {
291: // Rollback.
292: database_connection.rollback();
293: } else {
294: try {
295: // Otherwise commit.
296: database_connection.commit();
297: } catch (Throwable e) {
298: // Dispose this response if the commit failed.
299: disposeResult(response.getResultID());
300: // And throw the SQL Exception
301: throw handleExecuteThrowable(e, query);
302: }
303: }
304: } finally {
305: locker.finishMode(LockingMechanism.EXCLUSIVE_MODE);
306: }
307: }
308:
309: }
310:
311: }
312:
313: public void dispose() throws SQLException {
314: if (getUser() != null) {
315: DatabaseConnection database = getDatabaseConnection();
316: LockingMechanism locker = database.getLockingMechanism();
317: try {
318: // Lock into exclusive mode,
319: locker.setMode(LockingMechanism.EXCLUSIVE_MODE);
320: // Roll back any open transaction.
321: database.rollback();
322: } finally {
323: // Finish being in exclusive mode.
324: locker.finishMode(LockingMechanism.EXCLUSIVE_MODE);
325: // Close the database connection object.
326: database.close();
327: // Log out the user
328: getUser().logout();
329: // Call the internal dispose method.
330: internalDispose();
331: }
332: }
333: }
334:
335: }
|