/**
* Copyright (c) 2011-2014 Exxeleron GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.exxeleron.qjava;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* Base connector class for interfacing with the kdb+ service. Provides methods for synchronous and asynchronous
* interaction.
*/
public class QBasicConnection implements QConnection {
public static final String DEFAULT_ENCODING = "ISO-8859-1";
private final String host;
private final int port;
private final String username;
private final String password;
private final String encoding;
protected int protocolVersion;
protected Socket connection;
protected DataInputStream inputStream;
protected OutputStream outputStream;
protected QReader reader;
protected QWriter writer;
/**
* Initializes a new {@link QBasicConnection} instance.
*
* @param host
* Host of remote q service
* @param port
* Port of remote q service
* @param username
* Username for remote authorization
* @param password
* Password for remote authorization
* @param encoding
* Encoding used for serialization/deserialization of string objects
*/
public QBasicConnection(final String host, final int port, final String username, final String password, final String encoding) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.encoding = encoding;
this.reader = new DefaultQReader();
this.writer = new DefaultQWriter();
}
/**
* Initializes a new {@link QBasicConnection} instance with encoding set to "ISO-8859-1".
*
* @param host
* Host of remote q service
* @param port
* Port of remote q service
* @param username
* Username for remote authorization
* @param password
* Password for remote authorization
*/
public QBasicConnection(final String host, final int port, final String username, final String password) {
this(host, port, username, password, DEFAULT_ENCODING);
}
/**
* {@inheritDoc}
*/
public void open() throws IOException, QException {
if ( !isConnected() ) {
if ( host != null ) {
initSocket();
initialize();
reader.setStream(inputStream);
reader.setEncoding(encoding);
writer.setStream(outputStream);
writer.setEncoding(encoding);
writer.setProtocolVersion(protocolVersion);
} else {
throw new QConnectionException("Host cannot be null");
}
}
}
private void initSocket() throws UnknownHostException, IOException {
connection = new Socket(host, port);
connection.setTcpNoDelay(true);
inputStream = new DataInputStream(connection.getInputStream());
outputStream = connection.getOutputStream();
}
private void initialize() throws IOException, QException {
final String credentials = password != null ? String.format("%s:%s", username, password) : username;
byte[] request = (credentials + "\3\0").getBytes(encoding);
final byte[] response = new byte[2];
outputStream.write(request);
if ( inputStream.read(response, 0, 1) != 1 ) {
close();
initSocket();
request = (credentials + "\0").getBytes(encoding);
outputStream.write(request);
if ( inputStream.read(response, 0, 1) != 1 ) {
throw new QConnectionException("Connection denied.");
}
}
protocolVersion = Math.min(response[0], 3);
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
if ( isConnected() ) {
connection.close();
connection = null;
}
}
/**
* {@inheritDoc}
*/
public void reset() throws IOException, QException {
if ( connection != null ) {
connection.close();
}
connection = null;
open();
}
/**
* {@inheritDoc}
*/
public boolean isConnected() {
return connection != null && connection.isConnected();
}
/**
* {@inheritDoc}
*/
public Object sync( final String query, final Object... parameters ) throws QException, IOException {
query(QConnection.MessageType.SYNC, query, parameters);
final QMessage response = reader.read(false);
if ( response.getMessageType() == QConnection.MessageType.RESPONSE ) {
return response.getData();
} else {
writer.write(new QException("nyi: qJava expected response message"),
response.getMessageType() == QConnection.MessageType.ASYNC ? QConnection.MessageType.ASYNC : QConnection.MessageType.RESPONSE);
throw new QReaderException("Received message of type: " + response.getMessageType() + " where response was expected");
}
}
/**
* {@inheritDoc}
*/
public void async( final String query, final Object... parameters ) throws QException, IOException {
query(QConnection.MessageType.ASYNC, query, parameters);
}
/**
* {@inheritDoc}
*/
public int query( final QConnection.MessageType msgType, final String query, final Object... parameters ) throws QException, IOException {
if ( connection == null ) {
throw new IOException("Connection is not established.");
}
if ( parameters.length > 8 ) {
throw new QWriterException("Too many parameters.");
}
if ( parameters.length == 0 ) // simple string query
{
return writer.write(query.toCharArray(), msgType);
} else {
final Object[] request = new Object[parameters.length + 1];
request[0] = query.toCharArray();
int i = 1;
for ( final Object param : parameters ) {
request[i++] = param;
}
return writer.write(request, msgType);
}
}
/**
* {@inheritDoc}
*/
public Object receive( final boolean dataOnly, final boolean raw ) throws IOException, QException {
return dataOnly ? reader.read(raw).getData() : reader.read(raw);
}
/**
* {@inheritDoc}
*/
public Object receive() throws IOException, QException {
return receive(true, false);
}
/**
* Returns a String that represents the current {@link QBasicConnection}.
*
* @return a String that represents the current {@link QBasicConnection}
*/
@Override
public String toString() {
return String.format(":%s:%s", getHost(), getPort());
}
/**
* {@inheritDoc}
*/
public String getHost() {
return host;
}
/**
* {@inheritDoc}
*/
public int getPort() {
return port;
}
/**
* {@inheritDoc}
*/
public String getUsername() {
return username;
}
/**
* {@inheritDoc}
*/
public String getPassword() {
return password;
}
/**
* {@inheritDoc}
*/
public String getEncoding() {
return encoding;
}
/**
* {@inheritDoc}
*/
public int getProtocolVersion() {
return protocolVersion;
}
/**
* Retrieves {@link QReader} for IPC stream deserializing.
*
* @return {@link QReader} used for deserialization
*/
public QReader getReader() {
return reader;
}
/**
* Sets the {@link QReader} for IPC stream deserialization
*
* @param reader
* the {@link QReader}
*/
public void setReader( QReader reader ) {
this.reader = reader;
}
/**
* Retrieves {@link QWriter} used for serialization into IPC stream.
*
* @return {@link QWriter} used for serialization
*/
public QWriter getWriter() {
return writer;
}
/**
* Sets the {@link QWriter} used for serialization into IPC stream.
*
* @param writer
* the {@link QWriter}
*/
public void setWriter( QWriter writer ) {
this.writer = writer;
}
}