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.io.IOException;
009: import java.net.Socket;
010: import java.sql.SQLException;
011:
012: import org.h2.command.CommandInterface;
013: import org.h2.command.CommandRemote;
014: import org.h2.command.dml.SetTypes;
015: import org.h2.constant.ErrorCode;
016: import org.h2.constant.SysProperties;
017: import org.h2.jdbc.JdbcSQLException;
018: import org.h2.message.Message;
019: import org.h2.message.Trace;
020: import org.h2.message.TraceSystem;
021: import org.h2.store.DataHandler;
022: import org.h2.store.FileStore;
023: import org.h2.util.FileUtils;
024: import org.h2.util.NetUtils;
025: import org.h2.util.ObjectArray;
026: import org.h2.util.RandomUtils;
027: import org.h2.util.SmallLRUCache;
028: import org.h2.util.StringUtils;
029: import org.h2.value.Transfer;
030: import org.h2.value.Value;
031:
032: /**
033: * The client side part of a session when using the server mode.
034: * This object communicates with a Session on the server side.
035: */
036: public class SessionRemote implements SessionInterface, DataHandler {
037:
038: public static final int SESSION_PREPARE = 0;
039: public static final int SESSION_CLOSE = 1;
040: public static final int COMMAND_EXECUTE_QUERY = 2;
041: public static final int COMMAND_EXECUTE_UPDATE = 3;
042: public static final int COMMAND_CLOSE = 4;
043: public static final int RESULT_FETCH_ROWS = 5;
044: public static final int RESULT_RESET = 6;
045: public static final int RESULT_CLOSE = 7;
046: public static final int COMMAND_COMMIT = 8;
047: public static final int CHANGE_ID = 9;
048: public static final int COMMAND_GET_META_DATA = 10;
049:
050: public static final int STATUS_ERROR = 0;
051: public static final int STATUS_OK = 1;
052: public static final int STATUS_CLOSED = 2;
053:
054: private TraceSystem traceSystem;
055: private Trace trace;
056: private ObjectArray transferList;
057: private int nextId;
058: private boolean autoCommit = true;
059: private CommandInterface switchOffAutoCommit;
060: private ConnectionInfo connectionInfo;
061: private int objectId;
062: private String databaseName;
063: private String cipher;
064: private byte[] fileEncryptionKey;
065: private Object lobSyncObject = new Object();
066:
067: private Transfer initTransfer(ConnectionInfo ci, String db,
068: String server) throws IOException, SQLException {
069: Socket socket = NetUtils.createSocket(server,
070: Constants.DEFAULT_SERVER_PORT, ci.isSSL());
071: Transfer trans = new Transfer(this );
072: trans.setSocket(socket);
073: trans.init();
074: trans.writeInt(Constants.TCP_DRIVER_VERSION);
075: trans.writeString(db);
076: trans.writeString(ci.getOriginalURL());
077: trans.writeString(ci.getUserName());
078: trans.writeBytes(ci.getUserPasswordHash());
079: trans.writeBytes(ci.getFilePasswordHash());
080: String[] keys = ci.getKeys();
081: trans.writeInt(keys.length);
082: for (int i = 0; i < keys.length; i++) {
083: String key = keys[i];
084: trans.writeString(key).writeString(ci.getProperty(key));
085: }
086: try {
087: done(trans);
088: } catch (SQLException e) {
089: trans.close();
090: throw e;
091: }
092: autoCommit = true;
093: return trans;
094: }
095:
096: private void switchOffAutoCommitIfCluster() throws SQLException {
097: if (autoCommit && transferList.size() > 1) {
098: if (switchOffAutoCommit == null) {
099: switchOffAutoCommit = prepareCommand(
100: "SET AUTOCOMMIT FALSE", Integer.MAX_VALUE);
101: }
102: // this will call setAutoCommit(false)
103: switchOffAutoCommit.executeUpdate();
104: // so we need to switch it on
105: autoCommit = true;
106: }
107: }
108:
109: public void setAutoCommit(boolean autoCommit) {
110: this .autoCommit = autoCommit;
111: }
112:
113: public void autoCommitIfCluster() throws SQLException {
114: if (autoCommit && transferList != null
115: && transferList.size() > 1) {
116: // server side auto commit is off because of race conditions
117: // (update set id=1 where id=0, but update set id=2 where id=0 is
118: // faster)
119: for (int i = 0; i < transferList.size(); i++) {
120: Transfer transfer = (Transfer) transferList.get(i);
121: try {
122: traceOperation("COMMAND_COMMIT", 0);
123: transfer.writeInt(SessionRemote.COMMAND_COMMIT);
124: done(transfer);
125: } catch (IOException e) {
126: removeServer(i--);
127: }
128: }
129: }
130: }
131:
132: private String getTraceFilePrefix(String dbName)
133: throws SQLException {
134: String dir = SysProperties.CLIENT_TRACE_DIRECTORY;
135: StringBuffer buff = new StringBuffer();
136: buff.append(dir);
137: for (int i = 0; i < dbName.length(); i++) {
138: char ch = dbName.charAt(i);
139: if (Character.isLetterOrDigit(ch)) {
140: buff.append(ch);
141: } else {
142: buff.append('_');
143: }
144: }
145: return buff.toString();
146: }
147:
148: public SessionRemote() {
149: }
150:
151: public int getPowerOffCount() {
152: return 0;
153: }
154:
155: public void setPowerOffCount(int count) throws SQLException {
156: throw Message.getUnsupportedException();
157: }
158:
159: public SessionInterface createSession(ConnectionInfo ci)
160: throws SQLException {
161: return new SessionRemote(ci);
162: }
163:
164: private SessionRemote(ConnectionInfo ci) throws SQLException {
165: this .connectionInfo = ci;
166: connect();
167: }
168:
169: private void connect() throws SQLException {
170: ConnectionInfo ci = connectionInfo;
171: String name = ci.getName();
172: if (name.startsWith("//")) {
173: name = name.substring("//".length());
174: }
175: int idx = name.indexOf('/');
176: if (idx < 0) {
177: throw ci.getFormatException();
178: }
179: databaseName = name.substring(idx + 1);
180: String server = name.substring(0, idx);
181: traceSystem = new TraceSystem(null, false);
182: try {
183: String traceLevelFile = ci.getProperty(
184: SetTypes.TRACE_LEVEL_FILE, null);
185: if (traceLevelFile != null) {
186: int level = Integer.parseInt(traceLevelFile);
187: String prefix = getTraceFilePrefix(databaseName);
188: String file = FileUtils.createTempFile(prefix,
189: Constants.SUFFIX_TRACE_FILE, false, false);
190: traceSystem.setFileName(file);
191: traceSystem.setLevelFile(level);
192: }
193: String traceLevelSystemOut = ci.getProperty(
194: SetTypes.TRACE_LEVEL_SYSTEM_OUT, null);
195: if (traceLevelSystemOut != null) {
196: int level = Integer.parseInt(traceLevelSystemOut);
197: traceSystem.setLevelSystemOut(level);
198: }
199: } catch (Exception e) {
200: throw Message.convert(e);
201: }
202: trace = traceSystem.getTrace(Trace.JDBC);
203: transferList = new ObjectArray();
204: String serverlist = null;
205: if (server.indexOf(',') >= 0) {
206: serverlist = StringUtils.quoteStringSQL(server);
207: ci.setProperty("CLUSTER", serverlist);
208: }
209: cipher = ci.getProperty("CIPHER");
210: if (cipher != null) {
211: fileEncryptionKey = RandomUtils.getSecureBytes(32);
212: }
213: String[] servers = StringUtils.arraySplit(server, ',', true);
214: int len = servers.length;
215: transferList = new ObjectArray();
216: // TODO cluster: support at most 2 connections
217: boolean switchOffCluster = false;
218: try {
219: for (int i = 0; i < len; i++) {
220: try {
221: Transfer trans = initTransfer(ci, databaseName,
222: servers[i]);
223: transferList.add(trans);
224: } catch (IOException e) {
225: switchOffCluster = true;
226: }
227: }
228: checkClosed();
229: if (switchOffCluster) {
230: switchOffCluster();
231: }
232: switchOffAutoCommitIfCluster();
233: } catch (SQLException e) {
234: traceSystem.close();
235: throw e;
236: }
237: }
238:
239: private void switchOffCluster() throws SQLException {
240: CommandInterface ci = prepareCommand("SET CLUSTER ''",
241: Integer.MAX_VALUE);
242: ci.executeUpdate();
243: }
244:
245: public void removeServer(int i) throws SQLException {
246: transferList.remove(i);
247: checkClosed();
248: switchOffCluster();
249: }
250:
251: public CommandInterface prepareCommand(String sql, int fetchSize)
252: throws SQLException {
253: synchronized (this ) {
254: checkClosed();
255: return new CommandRemote(this , transferList, sql, fetchSize);
256: }
257: }
258:
259: public void checkClosed() throws SQLException {
260: if (isClosed()) {
261: // TODO broken connection: try to reconnect automatically
262: throw Message.getSQLException(ErrorCode.CONNECTION_BROKEN);
263: }
264: }
265:
266: public void close() {
267: if (transferList != null) {
268: synchronized (this ) {
269: for (int i = 0; i < transferList.size(); i++) {
270: Transfer transfer = (Transfer) transferList.get(i);
271: try {
272: traceOperation("SESSION_CLOSE", 0);
273: transfer.writeInt(SessionRemote.SESSION_CLOSE);
274: done(transfer);
275: transfer.close();
276: } catch (Exception e) {
277: trace.error("close", e);
278: }
279: }
280: }
281: transferList = null;
282: }
283: traceSystem.close();
284: }
285:
286: public Trace getTrace() {
287: return traceSystem.getTrace(Trace.JDBC);
288: }
289:
290: public int getNextId() {
291: return nextId++;
292: }
293:
294: public int getCurrentId() {
295: return nextId;
296: }
297:
298: public void done(Transfer transfer) throws SQLException,
299: IOException {
300: transfer.flush();
301: int status = transfer.readInt();
302: if (status == STATUS_ERROR) {
303: String sqlstate = transfer.readString();
304: String message = transfer.readString();
305: String sql = transfer.readString();
306: int errorCode = transfer.readInt();
307: String trace = transfer.readString();
308: throw new JdbcSQLException(message, sql, sqlstate,
309: errorCode, null, trace);
310: } else if (status == STATUS_CLOSED) {
311: transferList = null;
312: }
313: }
314:
315: public boolean isClustered() {
316: return transferList.size() > 1;
317: }
318:
319: public boolean isClosed() {
320: return transferList == null || transferList.size() == 0;
321: }
322:
323: public void traceOperation(String operation, int id) {
324: if (trace.debug()) {
325: trace.debug(operation + " " + id);
326: }
327: }
328:
329: public int allocateObjectId(boolean needFresh, boolean dataFile) {
330: return objectId++;
331: }
332:
333: public void checkPowerOff() throws SQLException {
334: }
335:
336: public void checkWritingAllowed() throws SQLException {
337: }
338:
339: public int compareTypeSave(Value a, Value b) throws SQLException {
340: throw Message.getInternalError();
341: }
342:
343: public String createTempFile() throws SQLException {
344: try {
345: return FileUtils.createTempFile(databaseName,
346: Constants.SUFFIX_TEMP_FILE, true, false);
347: } catch (IOException e) {
348: throw Message.convertIOException(e, databaseName);
349: }
350: }
351:
352: public void freeUpDiskSpace() throws SQLException {
353: }
354:
355: public int getChecksum(byte[] data, int start, int end) {
356: return 0;
357: }
358:
359: public String getDatabasePath() {
360: return "";
361: }
362:
363: public String getLobCompressionAlgorithm(int type) {
364: return null;
365: }
366:
367: public int getMaxLengthInplaceLob() {
368: return Constants.DEFAULT_MAX_LENGTH_CLIENTSIDE_LOB;
369: }
370:
371: public boolean getTextStorage() {
372: return false;
373: }
374:
375: public void handleInvalidChecksum() throws SQLException {
376: throw Message.getSQLException(ErrorCode.FILE_CORRUPTED_1,
377: "wrong checksum");
378: }
379:
380: public FileStore openFile(String name, String mode,
381: boolean mustExist) throws SQLException {
382: if (mustExist && !FileUtils.exists(name)) {
383: throw Message.getSQLException(ErrorCode.FILE_CORRUPTED_1,
384: name);
385: }
386: FileStore store;
387: byte[] magic = Constants.MAGIC_FILE_HEADER.getBytes();
388: if (cipher == null) {
389: store = FileStore.open(this , name, mode, magic);
390: } else {
391: store = FileStore.open(this , name, mode, magic, cipher,
392: fileEncryptionKey, 0);
393: }
394: store.setCheckedWriting(false);
395: try {
396: store.init();
397: } catch (SQLException e) {
398: store.closeSilently();
399: throw e;
400: }
401: return store;
402: }
403:
404: public DataHandler getDataHandler() {
405: return this ;
406: }
407:
408: public Object getLobSyncObject() {
409: return lobSyncObject;
410: }
411:
412: public void cancel() {
413: // TODO open another remote connection and cancel this session
414: // using a unique id (like PostgreSQL)
415: }
416:
417: public boolean getLobFilesInDirectories() {
418: return false;
419: }
420:
421: public SmallLRUCache getLobFileListCache() {
422: return null;
423: }
424:
425: }
|