| /* Copyright (c) 2001-2010, The HSQL Development Group |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * Neither the name of the HSQL Development Group nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, |
| * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| package org.hsqldb; |
| |
| import java.io.BufferedInputStream; |
| import java.io.DataInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.Socket; |
| import java.util.Calendar; |
| import java.util.GregorianCalendar; |
| import java.util.TimeZone; |
| |
| import org.hsqldb.error.Error; |
| import org.hsqldb.error.ErrorCode; |
| import org.hsqldb.lib.DataOutputStream; |
| import org.hsqldb.navigator.RowSetNavigatorClient; |
| import org.hsqldb.persist.HsqlProperties; |
| import org.hsqldb.result.Result; |
| import org.hsqldb.result.ResultConstants; |
| import org.hsqldb.result.ResultLob; |
| import org.hsqldb.rowio.RowInputBinary; |
| import org.hsqldb.rowio.RowOutputBinary; |
| import org.hsqldb.rowio.RowOutputInterface; |
| import org.hsqldb.server.HsqlSocketFactory; |
| import org.hsqldb.store.ValuePool; |
| import org.hsqldb.types.BlobDataID; |
| import org.hsqldb.types.ClobDataID; |
| import org.hsqldb.types.TimestampData; |
| |
| /** |
| * Base remote session proxy implementation. Uses instances of Result to |
| * transmit and recieve data. This implementation utilises the updated HSQL |
| * protocol. |
| * |
| * @author Fred Toussi (fredt@users dot sourceforge.net) |
| * @version 1.9.0 |
| * @since 1.7.2 |
| */ |
| public class ClientConnection implements SessionInterface { |
| |
| /** |
| * Specifies the Compatibility version required for both Servers and |
| * network JDBC Clients built with this baseline. Must remain public |
| * for Server to have visibility to it. |
| * |
| * Update this value only when the current version of HSQLDB does not |
| * have inter-compatibility with Server and network JDBC Driver of |
| * the previous HSQLDB version. |
| * |
| * Must specify all 4 version segments (any segment may be the value 0, |
| * however). The string elements at (position p from right counted from 0) |
| * are multiplied by 100 to power p and added up, then negated, to form the |
| * integer representation of version string. |
| */ |
| public static final String NETWORK_COMPATIBILITY_VERSION = "1.9.0.0"; |
| public static final int NETWORK_COMPATIBILITY_VERSION_INT = -1090000; |
| |
| // |
| static final int BUFFER_SIZE = 0x1000; |
| final byte[] mainBuffer = new byte[BUFFER_SIZE]; |
| private boolean isClosed; |
| private Socket socket; |
| protected DataOutputStream dataOutput; |
| protected DataInputStream dataInput; |
| protected RowOutputInterface rowOut; |
| protected RowInputBinary rowIn; |
| private Result resultOut; |
| private long sessionID; |
| private long lobIDSequence; |
| |
| // |
| private boolean isReadOnlyDefault = false; |
| private boolean isAutoCommit = true; |
| private int zoneSeconds; |
| private Scanner scanner; |
| private String zoneString; |
| private Calendar calendar; |
| |
| // |
| String host; |
| int port; |
| String path; |
| String database; |
| boolean isTLS; |
| int databaseID; |
| String clientPropertiesString; |
| HsqlProperties clientProperties; |
| |
| /** |
| * Establishes a connection to the server. |
| */ |
| public ClientConnection(String host, int port, String path, |
| String database, boolean isTLS, String user, |
| String password, int timeZoneSeconds) { |
| |
| this.host = host; |
| this.port = port; |
| this.path = path; |
| this.database = database; |
| this.isTLS = isTLS; |
| this.zoneSeconds = timeZoneSeconds; |
| this.zoneString = TimeZone.getDefault().getID(); |
| |
| initStructures(); |
| |
| Result login = Result.newConnectionAttemptRequest(user, password, |
| database, zoneString, timeZoneSeconds); |
| |
| initConnection(host, port, isTLS); |
| |
| Result resultIn = execute(login); |
| |
| if (resultIn.isError()) { |
| throw Error.error(resultIn); |
| } |
| |
| sessionID = resultIn.getSessionId(); |
| databaseID = resultIn.getDatabaseId(); |
| clientPropertiesString = resultIn.getMainString(); |
| } |
| |
| /** |
| * resultOut is reused to trasmit all remote calls for session management. |
| * Here the structure is preset for sending attributes. |
| */ |
| private void initStructures() { |
| |
| RowOutputBinary rowOutTemp = new RowOutputBinary(mainBuffer); |
| |
| rowOut = rowOutTemp; |
| rowIn = new RowInputBinary(rowOutTemp); |
| resultOut = Result.newSessionAttributesResult(); |
| } |
| |
| protected void initConnection(String host, int port, boolean isTLS) { |
| openConnection(host, port, isTLS); |
| } |
| |
| protected void openConnection(String host, int port, boolean isTLS) { |
| |
| try { |
| socket = HsqlSocketFactory.getInstance(isTLS).createSocket(host, |
| port); |
| |
| socket.setTcpNoDelay(true); |
| |
| dataOutput = new DataOutputStream(socket.getOutputStream()); |
| dataInput = new DataInputStream( |
| new BufferedInputStream(socket.getInputStream())); |
| |
| handshake(); |
| } catch (Exception e) { |
| |
| // The details from "e" should not be thrown away here. This is |
| // very useful info for end users to diagnose the runtime problem. |
| throw new HsqlException(e, Error.getStateString(ErrorCode.X_08001), |
| -ErrorCode.X_08001); |
| } |
| } |
| |
| protected void closeConnection() { |
| |
| try { |
| if (socket != null) { |
| socket.close(); |
| } |
| } catch (Exception e) {} |
| |
| socket = null; |
| } |
| |
| public synchronized Result execute(Result r) { |
| |
| try { |
| r.setSessionId(sessionID); |
| r.setDatabaseId(databaseID); |
| write(r); |
| |
| return read(); |
| } catch (Throwable e) { |
| throw Error.error(ErrorCode.X_08006, e.toString()); |
| } |
| } |
| |
| public synchronized RowSetNavigatorClient getRows(long navigatorId, |
| int offset, int size) { |
| |
| try { |
| resultOut.setResultType(ResultConstants.REQUESTDATA); |
| resultOut.setResultId(navigatorId); |
| resultOut.setUpdateCount(offset); |
| resultOut.setFetchSize(size); |
| |
| Result result = execute(resultOut); |
| |
| return (RowSetNavigatorClient) result.getNavigator(); |
| } catch (Throwable e) { |
| throw Error.error(ErrorCode.X_08006, e.toString()); |
| } |
| } |
| |
| public synchronized void closeNavigator(long navigatorId) { |
| |
| try { |
| resultOut.setResultType(ResultConstants.CLOSE_RESULT); |
| resultOut.setResultId(navigatorId); |
| execute(resultOut); |
| } catch (Throwable e) {} |
| } |
| |
| public synchronized void close() { |
| |
| if (isClosed) { |
| return; |
| } |
| |
| isClosed = true; |
| |
| try { |
| resultOut.setResultType(ResultConstants.DISCONNECT); |
| execute(resultOut); |
| } catch (Exception e) {} |
| |
| try { |
| closeConnection(); |
| } catch (Exception e) {} |
| } |
| |
| public synchronized Object getAttribute(int id) { |
| |
| resultOut.setResultType(ResultConstants.GETSESSIONATTR); |
| resultOut.setStatementType(id); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| |
| Object[] data = in.getSingleRowData(); |
| |
| switch (id) { |
| |
| case SessionInterface.INFO_AUTOCOMMIT : |
| return data[SessionInterface.INFO_BOOLEAN]; |
| |
| case SessionInterface.INFO_CONNECTION_READONLY : |
| return data[SessionInterface.INFO_BOOLEAN]; |
| |
| case SessionInterface.INFO_ISOLATION : |
| return data[SessionInterface.INFO_INTEGER]; |
| |
| case SessionInterface.INFO_CATALOG : |
| return data[SessionInterface.INFO_VARCHAR]; |
| } |
| |
| return null; |
| } |
| |
| public synchronized void setAttribute(int id, Object value) { |
| |
| resultOut.setResultType(ResultConstants.SETSESSIONATTR); |
| |
| Object[] data = resultOut.getSingleRowData(); |
| |
| data[SessionInterface.INFO_ID] = ValuePool.getInt(id); |
| |
| switch (id) { |
| |
| case SessionInterface.INFO_AUTOCOMMIT : |
| case SessionInterface.INFO_CONNECTION_READONLY : |
| data[SessionInterface.INFO_BOOLEAN] = value; |
| break; |
| |
| case SessionInterface.INFO_ISOLATION : |
| data[SessionInterface.INFO_INTEGER] = value; |
| break; |
| |
| case SessionInterface.INFO_CATALOG : |
| data[SessionInterface.INFO_VARCHAR] = value; |
| break; |
| } |
| |
| Result resultIn = execute(resultOut); |
| |
| if (resultIn.isError()) { |
| throw Error.error(resultIn); |
| } |
| } |
| |
| public synchronized boolean isReadOnlyDefault() { |
| |
| Object info = getAttribute(SessionInterface.INFO_CONNECTION_READONLY); |
| |
| isReadOnlyDefault = ((Boolean) info).booleanValue(); |
| |
| return isReadOnlyDefault; |
| } |
| |
| public synchronized void setReadOnlyDefault(boolean mode) { |
| |
| if (mode != isReadOnlyDefault) { |
| setAttribute(SessionInterface.INFO_CONNECTION_READONLY, |
| mode ? Boolean.TRUE |
| : Boolean.FALSE); |
| |
| isReadOnlyDefault = mode; |
| } |
| } |
| |
| public synchronized boolean isAutoCommit() { |
| |
| Object info = getAttribute(SessionInterface.INFO_AUTOCOMMIT); |
| |
| isAutoCommit = ((Boolean) info).booleanValue(); |
| |
| return isAutoCommit; |
| } |
| |
| public synchronized void setAutoCommit(boolean mode) { |
| |
| if (mode != isAutoCommit) { |
| setAttribute(SessionInterface.INFO_AUTOCOMMIT, mode ? Boolean.TRUE |
| : Boolean |
| .FALSE); |
| |
| isAutoCommit = mode; |
| } |
| } |
| |
| public synchronized void setIsolationDefault(int level) { |
| setAttribute(SessionInterface.INFO_ISOLATION, ValuePool.getInt(level)); |
| } |
| |
| public synchronized int getIsolation() { |
| |
| Object info = getAttribute(SessionInterface.INFO_ISOLATION); |
| |
| return ((Integer) info).intValue(); |
| } |
| |
| public synchronized boolean isClosed() { |
| return isClosed; |
| } |
| |
| public Session getSession() { |
| return null; |
| } |
| |
| public synchronized void startPhasedTransaction() {} |
| |
| public synchronized void prepareCommit() { |
| |
| resultOut.setAsTransactionEndRequest(ResultConstants.PREPARECOMMIT, |
| null); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public synchronized void commit(boolean chain) { |
| |
| resultOut.setAsTransactionEndRequest(ResultConstants.TX_COMMIT, null); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public synchronized void rollback(boolean chain) { |
| |
| resultOut.setAsTransactionEndRequest(ResultConstants.TX_ROLLBACK, |
| null); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public synchronized void rollbackToSavepoint(String name) { |
| |
| resultOut.setAsTransactionEndRequest( |
| ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK, name); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public synchronized void savepoint(String name) { |
| |
| Result result = Result.newSetSavepointRequest(name); |
| Result in = execute(result); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public synchronized void releaseSavepoint(String name) { |
| |
| resultOut.setAsTransactionEndRequest( |
| ResultConstants.TX_SAVEPOINT_NAME_RELEASE, name); |
| |
| Result in = execute(resultOut); |
| |
| if (in.isError()) { |
| throw Error.error(in); |
| } |
| } |
| |
| public void addWarning(HsqlException warning) {} |
| |
| public synchronized long getId() { |
| return sessionID; |
| } |
| |
| /** |
| * Used by pooled connections to reset the server-side session to a new |
| * one. In case of failure, the connection is closed. |
| * |
| * When the Connection.close() method is called, a pooled connection calls |
| * this method instead of HSQLClientConnection.close(). It can then |
| * reuse the HSQLClientConnection object with no further initialisation. |
| * |
| */ |
| public synchronized void resetSession() { |
| |
| Result login = Result.newResetSessionRequest(); |
| Result resultIn = execute(login); |
| |
| if (resultIn.isError()) { |
| isClosed = true; |
| |
| closeConnection(); |
| |
| throw Error.error(resultIn); |
| } |
| |
| sessionID = resultIn.getSessionId(); |
| databaseID = resultIn.getDatabaseId(); |
| } |
| |
| protected void write(Result r) throws IOException, HsqlException { |
| r.write(dataOutput, rowOut); |
| } |
| |
| protected Result read() throws IOException, HsqlException { |
| |
| Result result = Result.newResult(dataInput, rowIn); |
| |
| result.readAdditionalResults(this, dataInput, rowIn); |
| rowOut.setBuffer(mainBuffer); |
| rowIn.resetRow(mainBuffer.length); |
| |
| return result; |
| } |
| |
| /** |
| * Never called on this class |
| */ |
| public synchronized String getInternalConnectionURL() { |
| return null; |
| } |
| |
| public synchronized long getLobId() { |
| return lobIDSequence++; |
| } |
| |
| public BlobDataID createBlob(long length) { |
| |
| BlobDataID blob = new BlobDataID(getLobId()); |
| |
| return blob; |
| } |
| |
| public ClobDataID createClob(long length) { |
| |
| ClobDataID clob = new ClobDataID(getLobId()); |
| |
| return clob; |
| } |
| |
| /** |
| * Does nothing here |
| */ |
| public void allocateResultLob(ResultLob resultLob, |
| InputStream dataInput) {} |
| |
| public Scanner getScanner() { |
| |
| if (scanner == null) { |
| scanner = new Scanner(); |
| } |
| |
| return scanner; |
| } |
| |
| public Calendar getCalendar() { |
| |
| if (calendar == null) { |
| TimeZone zone = TimeZone.getTimeZone(zoneString); |
| |
| calendar = new GregorianCalendar(zone); |
| } |
| |
| return calendar; |
| } |
| |
| public TimestampData getCurrentDate() { |
| |
| long currentMillis = System.currentTimeMillis(); |
| long seconds = HsqlDateTime.getCurrentDateMillis(currentMillis) / 1000; |
| |
| return new TimestampData(seconds); |
| } |
| |
| public int getZoneSeconds() { |
| return zoneSeconds; |
| } |
| |
| public int getStreamBlockSize() { |
| return 512 * 1024; |
| } |
| |
| public HsqlProperties getClientProperties() { |
| |
| if (clientProperties == null) { |
| if (clientPropertiesString.length() > 0) { |
| HsqlProperties.delimitedArgPairsToProps(clientPropertiesString, |
| "=", ";", null); |
| } else { |
| clientProperties = new HsqlProperties(); |
| } |
| } |
| |
| return clientProperties; |
| } |
| |
| /** |
| * Converts specified encoded integer to a Network Compatibility Version |
| * String. The tranmitted integer is negative to distinguish it from |
| * 7 bit ASCII characters. |
| */ |
| static public String toNetCompVersionString(int i) { |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| i *= -1; |
| |
| sb.append(i / 1000000); |
| |
| i %= 1000000; |
| |
| sb.append('.'); |
| sb.append(i / 10000); |
| |
| i %= 10000; |
| |
| sb.append('.'); |
| sb.append(i / 100); |
| |
| i %= 100; |
| |
| sb.append('.'); |
| sb.append(i); |
| |
| return sb.toString(); |
| } |
| |
| protected void handshake() throws IOException { |
| dataOutput.writeInt(NETWORK_COMPATIBILITY_VERSION_INT); |
| dataOutput.flush(); |
| } |
| } |