/* 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(); } }