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 | */ |
6 | package org.h2.engine; |
7 | |
8 | import java.io.IOException; |
9 | import java.net.Socket; |
10 | import java.util.ArrayList; |
11 | |
12 | import org.h2.api.DatabaseEventListener; |
13 | import org.h2.api.ErrorCode; |
14 | import org.h2.api.JavaObjectSerializer; |
15 | import org.h2.command.CommandInterface; |
16 | import org.h2.command.CommandRemote; |
17 | import org.h2.command.dml.SetTypes; |
18 | import org.h2.jdbc.JdbcSQLException; |
19 | import org.h2.message.DbException; |
20 | import org.h2.message.Trace; |
21 | import org.h2.message.TraceSystem; |
22 | import org.h2.result.ResultInterface; |
23 | import org.h2.store.DataHandler; |
24 | import org.h2.store.FileStore; |
25 | import org.h2.store.LobStorageFrontend; |
26 | import org.h2.store.LobStorageInterface; |
27 | import org.h2.store.fs.FileUtils; |
28 | import org.h2.util.JdbcUtils; |
29 | import org.h2.util.MathUtils; |
30 | import org.h2.util.NetUtils; |
31 | import org.h2.util.New; |
32 | import org.h2.util.SmallLRUCache; |
33 | import org.h2.util.StringUtils; |
34 | import org.h2.util.TempFileDeleter; |
35 | import org.h2.value.Transfer; |
36 | import org.h2.value.Value; |
37 | |
38 | /** |
39 | * The client side part of a session when using the server mode. This object |
40 | * communicates with a Session on the server side. |
41 | */ |
42 | public class SessionRemote extends SessionWithState implements DataHandler { |
43 | |
44 | public static final int SESSION_PREPARE = 0; |
45 | public static final int SESSION_CLOSE = 1; |
46 | public static final int COMMAND_EXECUTE_QUERY = 2; |
47 | public static final int COMMAND_EXECUTE_UPDATE = 3; |
48 | public static final int COMMAND_CLOSE = 4; |
49 | public static final int RESULT_FETCH_ROWS = 5; |
50 | public static final int RESULT_RESET = 6; |
51 | public static final int RESULT_CLOSE = 7; |
52 | public static final int COMMAND_COMMIT = 8; |
53 | public static final int CHANGE_ID = 9; |
54 | public static final int COMMAND_GET_META_DATA = 10; |
55 | public static final int SESSION_PREPARE_READ_PARAMS = 11; |
56 | public static final int SESSION_SET_ID = 12; |
57 | public static final int SESSION_CANCEL_STATEMENT = 13; |
58 | public static final int SESSION_CHECK_KEY = 14; |
59 | public static final int SESSION_SET_AUTOCOMMIT = 15; |
60 | public static final int SESSION_HAS_PENDING_TRANSACTION = 16; |
61 | public static final int LOB_READ = 17; |
62 | |
63 | public static final int STATUS_ERROR = 0; |
64 | public static final int STATUS_OK = 1; |
65 | public static final int STATUS_CLOSED = 2; |
66 | public static final int STATUS_OK_STATE_CHANGED = 3; |
67 | |
68 | private static SessionFactory sessionFactory; |
69 | |
70 | private TraceSystem traceSystem; |
71 | private Trace trace; |
72 | private ArrayList<Transfer> transferList = New.arrayList(); |
73 | private int nextId; |
74 | private boolean autoCommit = true; |
75 | private CommandInterface autoCommitFalse, autoCommitTrue; |
76 | private ConnectionInfo connectionInfo; |
77 | private String databaseName; |
78 | private String cipher; |
79 | private byte[] fileEncryptionKey; |
80 | private final Object lobSyncObject = new Object(); |
81 | private String sessionId; |
82 | private int clientVersion; |
83 | private boolean autoReconnect; |
84 | private int lastReconnect; |
85 | private SessionInterface embedded; |
86 | private DatabaseEventListener eventListener; |
87 | private LobStorageFrontend lobStorage; |
88 | private boolean cluster; |
89 | private TempFileDeleter tempFileDeleter; |
90 | |
91 | private JavaObjectSerializer javaObjectSerializer; |
92 | private volatile boolean javaObjectSerializerInitialized; |
93 | |
94 | public SessionRemote(ConnectionInfo ci) { |
95 | this.connectionInfo = ci; |
96 | } |
97 | |
98 | @Override |
99 | public ArrayList<String> getClusterServers() { |
100 | ArrayList<String> serverList = new ArrayList<String>(); |
101 | for (int i = 0; i < transferList.size(); i++) { |
102 | Transfer transfer = transferList.get(i); |
103 | serverList.add(transfer.getSocket().getInetAddress(). |
104 | getHostAddress() + ":" + |
105 | transfer.getSocket().getPort()); |
106 | } |
107 | return serverList; |
108 | } |
109 | |
110 | private Transfer initTransfer(ConnectionInfo ci, String db, String server) |
111 | throws IOException { |
112 | Socket socket = NetUtils.createSocket(server, |
113 | Constants.DEFAULT_TCP_PORT, ci.isSSL()); |
114 | Transfer trans = new Transfer(this); |
115 | trans.setSocket(socket); |
116 | trans.setSSL(ci.isSSL()); |
117 | trans.init(); |
118 | trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6); |
119 | trans.writeInt(Constants.TCP_PROTOCOL_VERSION_15); |
120 | trans.writeString(db); |
121 | trans.writeString(ci.getOriginalURL()); |
122 | trans.writeString(ci.getUserName()); |
123 | trans.writeBytes(ci.getUserPasswordHash()); |
124 | trans.writeBytes(ci.getFilePasswordHash()); |
125 | String[] keys = ci.getKeys(); |
126 | trans.writeInt(keys.length); |
127 | for (String key : keys) { |
128 | trans.writeString(key).writeString(ci.getProperty(key)); |
129 | } |
130 | try { |
131 | done(trans); |
132 | clientVersion = trans.readInt(); |
133 | trans.setVersion(clientVersion); |
134 | if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_14) { |
135 | if (ci.getFileEncryptionKey() != null) { |
136 | trans.writeBytes(ci.getFileEncryptionKey()); |
137 | } |
138 | } |
139 | trans.writeInt(SessionRemote.SESSION_SET_ID); |
140 | trans.writeString(sessionId); |
141 | done(trans); |
142 | if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_15) { |
143 | autoCommit = trans.readBoolean(); |
144 | } else { |
145 | autoCommit = true; |
146 | } |
147 | return trans; |
148 | } catch (DbException e) { |
149 | trans.close(); |
150 | throw e; |
151 | } |
152 | } |
153 | |
154 | @Override |
155 | public boolean hasPendingTransaction() { |
156 | if (clientVersion < Constants.TCP_PROTOCOL_VERSION_10) { |
157 | return true; |
158 | } |
159 | for (int i = 0, count = 0; i < transferList.size(); i++) { |
160 | Transfer transfer = transferList.get(i); |
161 | try { |
162 | traceOperation("SESSION_HAS_PENDING_TRANSACTION", 0); |
163 | transfer.writeInt( |
164 | SessionRemote.SESSION_HAS_PENDING_TRANSACTION); |
165 | done(transfer); |
166 | return transfer.readInt() != 0; |
167 | } catch (IOException e) { |
168 | removeServer(e, i--, ++count); |
169 | } |
170 | } |
171 | return true; |
172 | } |
173 | |
174 | @Override |
175 | public void cancel() { |
176 | // this method is called when closing the connection |
177 | // the statement that is currently running is not canceled in this case |
178 | // however Statement.cancel is supported |
179 | } |
180 | |
181 | /** |
182 | * Cancel the statement with the given id. |
183 | * |
184 | * @param id the statement id |
185 | */ |
186 | public void cancelStatement(int id) { |
187 | for (Transfer transfer : transferList) { |
188 | try { |
189 | Transfer trans = transfer.openNewConnection(); |
190 | trans.init(); |
191 | trans.writeInt(clientVersion); |
192 | trans.writeInt(clientVersion); |
193 | trans.writeString(null); |
194 | trans.writeString(null); |
195 | trans.writeString(sessionId); |
196 | trans.writeInt(SessionRemote.SESSION_CANCEL_STATEMENT); |
197 | trans.writeInt(id); |
198 | trans.close(); |
199 | } catch (IOException e) { |
200 | trace.debug(e, "could not cancel statement"); |
201 | } |
202 | } |
203 | } |
204 | |
205 | private void checkClusterDisableAutoCommit(String serverList) { |
206 | if (autoCommit && transferList.size() > 1) { |
207 | setAutoCommitSend(false); |
208 | CommandInterface c = prepareCommand( |
209 | "SET CLUSTER " + serverList, Integer.MAX_VALUE); |
210 | // this will set autoCommit to false |
211 | c.executeUpdate(); |
212 | // so we need to switch it on |
213 | autoCommit = true; |
214 | cluster = true; |
215 | } |
216 | } |
217 | |
218 | @Override |
219 | public boolean getAutoCommit() { |
220 | return autoCommit; |
221 | } |
222 | |
223 | @Override |
224 | public void setAutoCommit(boolean autoCommit) { |
225 | if (!cluster) { |
226 | setAutoCommitSend(autoCommit); |
227 | } |
228 | this.autoCommit = autoCommit; |
229 | } |
230 | |
231 | public void setAutoCommitFromServer(boolean autoCommit) { |
232 | if (cluster) { |
233 | if (autoCommit) { |
234 | // the user executed SET AUTOCOMMIT TRUE |
235 | setAutoCommitSend(false); |
236 | this.autoCommit = true; |
237 | } |
238 | } else { |
239 | this.autoCommit = autoCommit; |
240 | } |
241 | } |
242 | |
243 | private void setAutoCommitSend(boolean autoCommit) { |
244 | if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_8) { |
245 | for (int i = 0, count = 0; i < transferList.size(); i++) { |
246 | Transfer transfer = transferList.get(i); |
247 | try { |
248 | traceOperation("SESSION_SET_AUTOCOMMIT", autoCommit ? 1 : 0); |
249 | transfer.writeInt(SessionRemote.SESSION_SET_AUTOCOMMIT). |
250 | writeBoolean(autoCommit); |
251 | done(transfer); |
252 | } catch (IOException e) { |
253 | removeServer(e, i--, ++count); |
254 | } |
255 | } |
256 | } else { |
257 | if (autoCommit) { |
258 | if (autoCommitTrue == null) { |
259 | autoCommitTrue = prepareCommand( |
260 | "SET AUTOCOMMIT TRUE", Integer.MAX_VALUE); |
261 | } |
262 | autoCommitTrue.executeUpdate(); |
263 | } else { |
264 | if (autoCommitFalse == null) { |
265 | autoCommitFalse = prepareCommand( |
266 | "SET AUTOCOMMIT FALSE", Integer.MAX_VALUE); |
267 | } |
268 | autoCommitFalse.executeUpdate(); |
269 | } |
270 | } |
271 | } |
272 | |
273 | /** |
274 | * Calls COMMIT if the session is in cluster mode. |
275 | */ |
276 | public void autoCommitIfCluster() { |
277 | if (autoCommit && cluster) { |
278 | // server side auto commit is off because of race conditions |
279 | // (update set id=1 where id=0, but update set id=2 where id=0 is |
280 | // faster) |
281 | for (int i = 0, count = 0; i < transferList.size(); i++) { |
282 | Transfer transfer = transferList.get(i); |
283 | try { |
284 | traceOperation("COMMAND_COMMIT", 0); |
285 | transfer.writeInt(SessionRemote.COMMAND_COMMIT); |
286 | done(transfer); |
287 | } catch (IOException e) { |
288 | removeServer(e, i--, ++count); |
289 | } |
290 | } |
291 | } |
292 | } |
293 | |
294 | private String getFilePrefix(String dir) { |
295 | StringBuilder buff = new StringBuilder(dir); |
296 | buff.append('/'); |
297 | for (int i = 0; i < databaseName.length(); i++) { |
298 | char ch = databaseName.charAt(i); |
299 | if (Character.isLetterOrDigit(ch)) { |
300 | buff.append(ch); |
301 | } else { |
302 | buff.append('_'); |
303 | } |
304 | } |
305 | return buff.toString(); |
306 | } |
307 | |
308 | @Override |
309 | public int getPowerOffCount() { |
310 | return 0; |
311 | } |
312 | |
313 | @Override |
314 | public void setPowerOffCount(int count) { |
315 | throw DbException.getUnsupportedException("remote"); |
316 | } |
317 | |
318 | /** |
319 | * Open a new (remote or embedded) session. |
320 | * |
321 | * @param openNew whether to open a new session in any case |
322 | * @return the session |
323 | */ |
324 | public SessionInterface connectEmbeddedOrServer(boolean openNew) { |
325 | ConnectionInfo ci = connectionInfo; |
326 | if (ci.isRemote()) { |
327 | connectServer(ci); |
328 | return this; |
329 | } |
330 | // create the session using reflection, |
331 | // so that the JDBC layer can be compiled without it |
332 | boolean autoServerMode = Boolean.parseBoolean( |
333 | ci.getProperty("AUTO_SERVER", "false")); |
334 | ConnectionInfo backup = null; |
335 | try { |
336 | if (autoServerMode) { |
337 | backup = ci.clone(); |
338 | connectionInfo = ci.clone(); |
339 | } |
340 | if (openNew) { |
341 | ci.setProperty("OPEN_NEW", "true"); |
342 | } |
343 | if (sessionFactory == null) { |
344 | sessionFactory = (SessionFactory) Class.forName( |
345 | "org.h2.engine.Engine").getMethod("getInstance").invoke(null); |
346 | } |
347 | return sessionFactory.createSession(ci); |
348 | } catch (Exception re) { |
349 | DbException e = DbException.convert(re); |
350 | if (e.getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1) { |
351 | if (autoServerMode) { |
352 | String serverKey = ((JdbcSQLException) e.getSQLException()). |
353 | getSQL(); |
354 | if (serverKey != null) { |
355 | backup.setServerKey(serverKey); |
356 | // OPEN_NEW must be removed now, otherwise |
357 | // opening a session with AUTO_SERVER fails |
358 | // if another connection is already open |
359 | backup.removeProperty("OPEN_NEW", null); |
360 | connectServer(backup); |
361 | return this; |
362 | } |
363 | } |
364 | } |
365 | throw e; |
366 | } |
367 | } |
368 | |
369 | private void connectServer(ConnectionInfo ci) { |
370 | String name = ci.getName(); |
371 | if (name.startsWith("//")) { |
372 | name = name.substring("//".length()); |
373 | } |
374 | int idx = name.indexOf('/'); |
375 | if (idx < 0) { |
376 | throw ci.getFormatException(); |
377 | } |
378 | databaseName = name.substring(idx + 1); |
379 | String server = name.substring(0, idx); |
380 | traceSystem = new TraceSystem(null); |
381 | String traceLevelFile = ci.getProperty( |
382 | SetTypes.TRACE_LEVEL_FILE, null); |
383 | if (traceLevelFile != null) { |
384 | int level = Integer.parseInt(traceLevelFile); |
385 | String prefix = getFilePrefix( |
386 | SysProperties.CLIENT_TRACE_DIRECTORY); |
387 | try { |
388 | traceSystem.setLevelFile(level); |
389 | if (level > 0 && level < 4) { |
390 | String file = FileUtils.createTempFile(prefix, |
391 | Constants.SUFFIX_TRACE_FILE, false, false); |
392 | traceSystem.setFileName(file); |
393 | } |
394 | } catch (IOException e) { |
395 | throw DbException.convertIOException(e, prefix); |
396 | } |
397 | } |
398 | String traceLevelSystemOut = ci.getProperty( |
399 | SetTypes.TRACE_LEVEL_SYSTEM_OUT, null); |
400 | if (traceLevelSystemOut != null) { |
401 | int level = Integer.parseInt(traceLevelSystemOut); |
402 | traceSystem.setLevelSystemOut(level); |
403 | } |
404 | trace = traceSystem.getTrace(Trace.JDBC); |
405 | String serverList = null; |
406 | if (server.indexOf(',') >= 0) { |
407 | serverList = StringUtils.quoteStringSQL(server); |
408 | ci.setProperty("CLUSTER", Constants.CLUSTERING_ENABLED); |
409 | } |
410 | autoReconnect = Boolean.parseBoolean(ci.getProperty( |
411 | "AUTO_RECONNECT", "false")); |
412 | // AUTO_SERVER implies AUTO_RECONNECT |
413 | boolean autoServer = Boolean.parseBoolean(ci.getProperty( |
414 | "AUTO_SERVER", "false")); |
415 | if (autoServer && serverList != null) { |
416 | throw DbException |
417 | .getUnsupportedException("autoServer && serverList != null"); |
418 | } |
419 | autoReconnect |= autoServer; |
420 | if (autoReconnect) { |
421 | String className = ci.getProperty("DATABASE_EVENT_LISTENER"); |
422 | if (className != null) { |
423 | className = StringUtils.trim(className, true, true, "'"); |
424 | try { |
425 | eventListener = (DatabaseEventListener) JdbcUtils |
426 | .loadUserClass(className).newInstance(); |
427 | } catch (Throwable e) { |
428 | throw DbException.convert(e); |
429 | } |
430 | } |
431 | } |
432 | cipher = ci.getProperty("CIPHER"); |
433 | if (cipher != null) { |
434 | fileEncryptionKey = MathUtils.secureRandomBytes(32); |
435 | } |
436 | String[] servers = StringUtils.arraySplit(server, ',', true); |
437 | int len = servers.length; |
438 | transferList.clear(); |
439 | sessionId = StringUtils.convertBytesToHex(MathUtils.secureRandomBytes(32)); |
440 | // TODO cluster: support more than 2 connections |
441 | boolean switchOffCluster = false; |
442 | try { |
443 | for (int i = 0; i < len; i++) { |
444 | String s = servers[i]; |
445 | try { |
446 | Transfer trans = initTransfer(ci, databaseName, s); |
447 | transferList.add(trans); |
448 | } catch (IOException e) { |
449 | if (len == 1) { |
450 | throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, e, e + ": " + s); |
451 | } |
452 | switchOffCluster = true; |
453 | } |
454 | } |
455 | checkClosed(); |
456 | if (switchOffCluster) { |
457 | switchOffCluster(); |
458 | } |
459 | checkClusterDisableAutoCommit(serverList); |
460 | } catch (DbException e) { |
461 | traceSystem.close(); |
462 | throw e; |
463 | } |
464 | } |
465 | |
466 | private void switchOffCluster() { |
467 | CommandInterface ci = prepareCommand("SET CLUSTER ''", Integer.MAX_VALUE); |
468 | ci.executeUpdate(); |
469 | } |
470 | |
471 | /** |
472 | * Remove a server from the list of cluster nodes and disables the cluster |
473 | * mode. |
474 | * |
475 | * @param e the exception (used for debugging) |
476 | * @param i the index of the server to remove |
477 | * @param count the retry count index |
478 | */ |
479 | public void removeServer(IOException e, int i, int count) { |
480 | trace.debug(e, "removing server because of exception"); |
481 | transferList.remove(i); |
482 | if (transferList.size() == 0 && autoReconnect(count)) { |
483 | return; |
484 | } |
485 | checkClosed(); |
486 | switchOffCluster(); |
487 | } |
488 | |
489 | @Override |
490 | public synchronized CommandInterface prepareCommand(String sql, int fetchSize) { |
491 | checkClosed(); |
492 | return new CommandRemote(this, transferList, sql, fetchSize); |
493 | } |
494 | |
495 | /** |
496 | * Automatically re-connect if necessary and if configured to do so. |
497 | * |
498 | * @param count the retry count index |
499 | * @return true if reconnected |
500 | */ |
501 | private boolean autoReconnect(int count) { |
502 | if (!isClosed()) { |
503 | return false; |
504 | } |
505 | if (!autoReconnect) { |
506 | return false; |
507 | } |
508 | if (!cluster && !autoCommit) { |
509 | return false; |
510 | } |
511 | if (count > SysProperties.MAX_RECONNECT) { |
512 | return false; |
513 | } |
514 | lastReconnect++; |
515 | while (true) { |
516 | try { |
517 | embedded = connectEmbeddedOrServer(false); |
518 | break; |
519 | } catch (DbException e) { |
520 | if (e.getErrorCode() != ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE) { |
521 | throw e; |
522 | } |
523 | // exclusive mode: re-try endlessly |
524 | try { |
525 | Thread.sleep(500); |
526 | } catch (Exception e2) { |
527 | // ignore |
528 | } |
529 | } |
530 | } |
531 | if (embedded == this) { |
532 | // connected to a server somewhere else |
533 | embedded = null; |
534 | } else { |
535 | // opened an embedded connection now - |
536 | // must connect to this database in server mode |
537 | // unfortunately |
538 | connectEmbeddedOrServer(true); |
539 | } |
540 | recreateSessionState(); |
541 | if (eventListener != null) { |
542 | eventListener.setProgress(DatabaseEventListener.STATE_RECONNECTED, |
543 | databaseName, count, SysProperties.MAX_RECONNECT); |
544 | } |
545 | return true; |
546 | } |
547 | |
548 | /** |
549 | * Check if this session is closed and throws an exception if so. |
550 | * |
551 | * @throws DbException if the session is closed |
552 | */ |
553 | public void checkClosed() { |
554 | if (isClosed()) { |
555 | throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); |
556 | } |
557 | } |
558 | |
559 | @Override |
560 | public void close() { |
561 | RuntimeException closeError = null; |
562 | if (transferList != null) { |
563 | synchronized (this) { |
564 | for (Transfer transfer : transferList) { |
565 | try { |
566 | traceOperation("SESSION_CLOSE", 0); |
567 | transfer.writeInt(SessionRemote.SESSION_CLOSE); |
568 | done(transfer); |
569 | transfer.close(); |
570 | } catch (RuntimeException e) { |
571 | trace.error(e, "close"); |
572 | closeError = e; |
573 | } catch (Exception e) { |
574 | trace.error(e, "close"); |
575 | } |
576 | } |
577 | } |
578 | transferList = null; |
579 | } |
580 | traceSystem.close(); |
581 | if (embedded != null) { |
582 | embedded.close(); |
583 | embedded = null; |
584 | } |
585 | if (closeError != null) { |
586 | throw closeError; |
587 | } |
588 | } |
589 | |
590 | @Override |
591 | public Trace getTrace() { |
592 | return traceSystem.getTrace(Trace.JDBC); |
593 | } |
594 | |
595 | public int getNextId() { |
596 | return nextId++; |
597 | } |
598 | |
599 | public int getCurrentId() { |
600 | return nextId; |
601 | } |
602 | |
603 | /** |
604 | * Called to flush the output after data has been sent to the server and |
605 | * just before receiving data. This method also reads the status code from |
606 | * the server and throws any exception the server sent. |
607 | * |
608 | * @param transfer the transfer object |
609 | * @throws DbException if the server sent an exception |
610 | * @throws IOException if there is a communication problem between client |
611 | * and server |
612 | */ |
613 | public void done(Transfer transfer) throws IOException { |
614 | transfer.flush(); |
615 | int status = transfer.readInt(); |
616 | if (status == STATUS_ERROR) { |
617 | String sqlstate = transfer.readString(); |
618 | String message = transfer.readString(); |
619 | String sql = transfer.readString(); |
620 | int errorCode = transfer.readInt(); |
621 | String stackTrace = transfer.readString(); |
622 | JdbcSQLException s = new JdbcSQLException(message, sql, sqlstate, |
623 | errorCode, null, stackTrace); |
624 | if (errorCode == ErrorCode.CONNECTION_BROKEN_1) { |
625 | // allow re-connect |
626 | IOException e = new IOException(s.toString(), s); |
627 | throw e; |
628 | } |
629 | throw DbException.convert(s); |
630 | } else if (status == STATUS_CLOSED) { |
631 | transferList = null; |
632 | } else if (status == STATUS_OK_STATE_CHANGED) { |
633 | sessionStateChanged = true; |
634 | } else if (status == STATUS_OK) { |
635 | // ok |
636 | } else { |
637 | throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, |
638 | "unexpected status " + status); |
639 | } |
640 | } |
641 | |
642 | /** |
643 | * Returns true if the connection was opened in cluster mode. |
644 | * |
645 | * @return true if it is |
646 | */ |
647 | public boolean isClustered() { |
648 | return cluster; |
649 | } |
650 | |
651 | @Override |
652 | public boolean isClosed() { |
653 | return transferList == null || transferList.size() == 0; |
654 | } |
655 | |
656 | /** |
657 | * Write the operation to the trace system if debug trace is enabled. |
658 | * |
659 | * @param operation the operation performed |
660 | * @param id the id of the operation |
661 | */ |
662 | public void traceOperation(String operation, int id) { |
663 | if (trace.isDebugEnabled()) { |
664 | trace.debug("{0} {1}", operation, id); |
665 | } |
666 | } |
667 | |
668 | @Override |
669 | public void checkPowerOff() { |
670 | // ok |
671 | } |
672 | |
673 | @Override |
674 | public void checkWritingAllowed() { |
675 | // ok |
676 | } |
677 | |
678 | @Override |
679 | public String getDatabasePath() { |
680 | return ""; |
681 | } |
682 | |
683 | @Override |
684 | public String getLobCompressionAlgorithm(int type) { |
685 | return null; |
686 | } |
687 | |
688 | @Override |
689 | public int getMaxLengthInplaceLob() { |
690 | return SysProperties.LOB_CLIENT_MAX_SIZE_MEMORY; |
691 | } |
692 | |
693 | @Override |
694 | public FileStore openFile(String name, String mode, boolean mustExist) { |
695 | if (mustExist && !FileUtils.exists(name)) { |
696 | throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, name); |
697 | } |
698 | FileStore store; |
699 | if (cipher == null) { |
700 | store = FileStore.open(this, name, mode); |
701 | } else { |
702 | store = FileStore.open(this, name, mode, cipher, fileEncryptionKey, 0); |
703 | } |
704 | store.setCheckedWriting(false); |
705 | try { |
706 | store.init(); |
707 | } catch (DbException e) { |
708 | store.closeSilently(); |
709 | throw e; |
710 | } |
711 | return store; |
712 | } |
713 | |
714 | @Override |
715 | public DataHandler getDataHandler() { |
716 | return this; |
717 | } |
718 | |
719 | @Override |
720 | public Object getLobSyncObject() { |
721 | return lobSyncObject; |
722 | } |
723 | |
724 | @Override |
725 | public SmallLRUCache<String, String[]> getLobFileListCache() { |
726 | return null; |
727 | } |
728 | |
729 | public int getLastReconnect() { |
730 | return lastReconnect; |
731 | } |
732 | |
733 | @Override |
734 | public TempFileDeleter getTempFileDeleter() { |
735 | if (tempFileDeleter == null) { |
736 | tempFileDeleter = TempFileDeleter.getInstance(); |
737 | } |
738 | return tempFileDeleter; |
739 | } |
740 | |
741 | @Override |
742 | public boolean isReconnectNeeded(boolean write) { |
743 | return false; |
744 | } |
745 | |
746 | @Override |
747 | public SessionInterface reconnect(boolean write) { |
748 | return this; |
749 | } |
750 | |
751 | @Override |
752 | public void afterWriting() { |
753 | // nothing to do |
754 | } |
755 | |
756 | @Override |
757 | public LobStorageInterface getLobStorage() { |
758 | if (lobStorage == null) { |
759 | lobStorage = new LobStorageFrontend(this); |
760 | } |
761 | return lobStorage; |
762 | } |
763 | |
764 | @Override |
765 | public synchronized int readLob(long lobId, byte[] hmac, long offset, |
766 | byte[] buff, int off, int length) { |
767 | checkClosed(); |
768 | for (int i = 0, count = 0; i < transferList.size(); i++) { |
769 | Transfer transfer = transferList.get(i); |
770 | try { |
771 | traceOperation("LOB_READ", (int) lobId); |
772 | transfer.writeInt(SessionRemote.LOB_READ); |
773 | transfer.writeLong(lobId); |
774 | if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_12) { |
775 | transfer.writeBytes(hmac); |
776 | } |
777 | transfer.writeLong(offset); |
778 | transfer.writeInt(length); |
779 | done(transfer); |
780 | length = transfer.readInt(); |
781 | if (length <= 0) { |
782 | return length; |
783 | } |
784 | transfer.readBytes(buff, off, length); |
785 | return length; |
786 | } catch (IOException e) { |
787 | removeServer(e, i--, ++count); |
788 | } |
789 | } |
790 | return 1; |
791 | } |
792 | |
793 | @Override |
794 | public JavaObjectSerializer getJavaObjectSerializer() { |
795 | initJavaObjectSerializer(); |
796 | return javaObjectSerializer; |
797 | } |
798 | |
799 | private void initJavaObjectSerializer() { |
800 | if (javaObjectSerializerInitialized) { |
801 | return; |
802 | } |
803 | synchronized (this) { |
804 | if (javaObjectSerializerInitialized) { |
805 | return; |
806 | } |
807 | String serializerFQN = readSerializationSettings(); |
808 | if (serializerFQN != null) { |
809 | serializerFQN = serializerFQN.trim(); |
810 | if (!serializerFQN.isEmpty() && !serializerFQN.equals("null")) { |
811 | try { |
812 | javaObjectSerializer = (JavaObjectSerializer) JdbcUtils |
813 | .loadUserClass(serializerFQN).newInstance(); |
814 | } catch (Exception e) { |
815 | throw DbException.convert(e); |
816 | } |
817 | } |
818 | } |
819 | javaObjectSerializerInitialized = true; |
820 | } |
821 | } |
822 | |
823 | /** |
824 | * Read the serializer name from the persistent database settings. |
825 | * |
826 | * @return the serializer |
827 | */ |
828 | private String readSerializationSettings() { |
829 | String javaObjectSerializerFQN = null; |
830 | CommandInterface ci = prepareCommand( |
831 | "SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS "+ |
832 | " WHERE NAME='JAVA_OBJECT_SERIALIZER'", Integer.MAX_VALUE); |
833 | try { |
834 | ResultInterface result = ci.executeQuery(0, false); |
835 | if (result.next()) { |
836 | Value[] row = result.currentRow(); |
837 | javaObjectSerializerFQN = row[0].getString(); |
838 | } |
839 | } finally { |
840 | ci.close(); |
841 | } |
842 | return javaObjectSerializerFQN; |
843 | } |
844 | |
845 | @Override |
846 | public void addTemporaryLob(Value v) { |
847 | // do nothing |
848 | } |
849 | } |