/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
import org.voltdb.client.ClientResponse;
import org.voltdb.exceptions.SerializableException;
import org.voltdb.messaging.FastDeserializer;
import org.voltdb.messaging.FastSerializable;
import org.voltdb.messaging.FastSerializer;
import org.voltdb.types.SpeculationType;
import edu.brown.hstore.HStoreConstants;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.txns.AbstractTransaction;
import edu.brown.hstore.txns.LocalTransaction;
import edu.brown.utils.StringUtil;
/**
* Packages up the data to be sent back to the client as a stored
* procedure response in one FastSerialziable object.
*
*/
public class ClientResponseImpl implements FastSerializable, ClientResponse {
private boolean setProperly = false;
private Status status;
private String statusString = null;
private byte appStatus = Byte.MIN_VALUE;
private String appStatusString = null;
private VoltTable[] results = HStoreConstants.EMPTY_RESULT;
private int clusterRoundTripTime = -1;
private int clientRoundTripTime = -1;
private SerializableException m_exception = null;
// PAVLO
private long txn_id;
private boolean singlepartition = true;
private int basePartition = -1;
private int restartCounter = 0;
private SpeculationType speculative = SpeculationType.NULL;
private ClientResponseDebug debug = null;
/** opaque data optionally provided by and returned to the client */
private long clientHandle = -1;
// ARIES
private byte[] arieslogData = null;
public ClientResponseImpl() {}
/**
*
* @param txn_id
* @param client_handle
* @param basePartition
* @param status
* @param results
* @param statusString
*/
public ClientResponseImpl(long txn_id, long client_handle, int basePartition, Status status, VoltTable[] results, String statusString) {
this(txn_id, client_handle, basePartition, status, Byte.MIN_VALUE, null, results, statusString, null);
}
/**
* And another....
* @param status
* @param results
* @param extra
* @param e
*/
public ClientResponseImpl(long txn_id, long client_handle, int basePartition, Status status, VoltTable[] results, String statusString, SerializableException e) {
this(txn_id, client_handle, basePartition, status, Byte.MIN_VALUE, null, results, statusString, e);
}
public ClientResponseImpl(long txn_id, long client_handle, int basePartition, Status status, byte appStatus, String appStatusString, VoltTable[] results, String statusString, SerializableException e) {
this.init(txn_id, client_handle, basePartition, status, appStatus, appStatusString, results, statusString, e);
}
@Deprecated
public void init(Long txn_id, long client_handle, int basePartition, Status status, byte appStatus, String appStatusString, VoltTable[] results, String statusString, SerializableException e) {
this.txn_id = txn_id.longValue();
this.clientHandle = client_handle;
this.basePartition = basePartition;
this.appStatus = appStatus;
this.appStatusString = appStatusString;
setResults(status, results, statusString, e);
}
public void init(LocalTransaction ts, Status status, VoltTable[] results, String statusString) {
this.init(ts, status, Byte.MIN_VALUE, null, results, statusString, ts.getPendingError());
}
public void init(LocalTransaction ts,
Status status,
byte appStatus,
String appStatusString,
VoltTable[] results,
String statusString,
SerializableException e) {
this.txn_id = ts.getTransactionId().longValue();
this.clientHandle = ts.getClientHandle();
this.basePartition = ts.getBasePartition();
this.appStatus = appStatus;
this.appStatusString = appStatusString;
this.restartCounter = ts.getRestartCounter();
this.singlepartition = ts.isPredictSinglePartition();
this.speculative = ts.getSpeculationType();
this.setResults(status, results, statusString, e);
}
@Override
public boolean isInitialized() {
return (this.txn_id != -1);
}
@Override
public void finish() {
this.txn_id = -1;
this.clientHandle = -1;
this.status = null;
this.results = null;
this.restartCounter = 0;
}
private void setResults(Status status, VoltTable[] results, String statusString) {
assert results != null;
for (VoltTable result : results) {
// null values are not permitted in results. If there is one, it will cause an
// exception in writeExternal. This throws the exception sooner.
assert result != null;
}
this.status = status;
this.results = results;
this.statusString = statusString;
this.setProperly = true;
}
private void setResults(Status status, VoltTable[] results, String extra, SerializableException e) {
m_exception = e;
setResults(status, results, extra);
}
public void setStatus(Status status) {
this.status = status;
}
@Override
public int getBasePartition() {
return (this.basePartition);
}
public void setBasePartition(int val) {
this.basePartition = val;
}
@Override
public boolean isSinglePartition() {
return singlepartition;
}
public void setSinglePartition(boolean val) {
this.singlepartition = val;
}
@Override
public Status getStatus() {
return status;
}
public VoltTable[] getResults() {
return results;
}
@Override
public int getResultsSize() {
int ret = 0;
for (VoltTable vt : this.results) {
ret += vt.getUnderlyingBufferSize();
} // FOR
return ret;
}
public String getStatusString() {
return statusString;
}
public void setClientHandle(long aHandle) {
clientHandle = aHandle;
}
public long getClientHandle() {
return clientHandle;
}
public SerializableException getException() {
return m_exception;
}
@Override
public long getTransactionId() {
return (this.txn_id);
}
@Override
public int getClusterRoundtrip() {
return clusterRoundTripTime;
}
public void setClusterRoundtrip(int time) {
clusterRoundTripTime = time;
}
@Override
public int getClientRoundtrip() {
return clientRoundTripTime;
}
public void setClientRoundtrip(int time) {
clientRoundTripTime = time;
}
@Override
public byte getAppStatus() {
return appStatus;
}
@Override
public String getAppStatusString() {
return appStatusString;
}
@Override
public int getRestartCounter() {
return restartCounter;
}
public void setRestartCounter(int restarts) {
restartCounter = restarts;
}
@Override
public boolean isSpeculative() {
return (this.speculative != SpeculationType.NULL);
}
// ----------------------------------------------------------------------------
// SPECIAL DEBUG HANDLE
// ----------------------------------------------------------------------------
@Override
public ClientResponseDebug getDebug() {
return (this.debug);
}
@Override
public boolean hasDebug() {
return (this.debug != null);
}
public void setDebug(ClientResponseDebug debug) {
this.debug = debug;
}
// ----------------------------------------------------------------------------
// SERIALIZATION METHODS
// ----------------------------------------------------------------------------
@Override
public void readExternal(FastDeserializer in) throws IOException {
in.readByte();//Skip version byte // 1 byte
this.restartCounter = in.readByte(); // 1 byte
this.txn_id = in.readLong(); // 8 bytes
this.clientHandle = in.readLong(); // 8 bytes
this.singlepartition = in.readBoolean(); // 1 byte
this.basePartition = in.readInt(); // 4 bytes
this.speculative = SpeculationType.get(in.readByte()); // 1 byte
this.status = Status.valueOf(in.readByte()); // 1 byte
byte presentFields = in.readByte(); // 1 byte
if ((presentFields & (1 << 5)) != 0) {
statusString = in.readString();
} else {
statusString = null;
}
appStatus = in.readByte();
if ((presentFields & (1 << 7)) != 0) {
appStatusString = in.readString();
} else {
appStatusString = null;
}
clusterRoundTripTime = in.readInt();
if ((presentFields & (1 << 6)) != 0) {
m_exception = SerializableException.deserializeFromBuffer(in.buffer());
} else {
m_exception = null;
}
results = (VoltTable[]) in.readArray(VoltTable.class);
setProperly = true;
if (in.readBoolean()) {
this.debug = new ClientResponseDebug();
this.debug.readExternal(in);
}
}
@Override
public void writeExternal(FastSerializer out) throws IOException {
assert setProperly;
out.writeByte(0);//version
out.writeByte(this.restartCounter);
out.writeLong(this.txn_id);
out.writeLong(this.clientHandle);
out.writeBoolean(this.singlepartition);
out.writeInt(this.basePartition);
out.write((byte)this.speculative.ordinal());
out.write((byte)this.status.ordinal());
byte presentFields = 0;
if (appStatusString != null) {
presentFields |= 1 << 7;
}
if (m_exception != null) {
presentFields |= 1 << 6;
}
if (statusString != null) {
presentFields |= 1 << 5;
}
out.writeByte(presentFields);
if (statusString != null) {
out.writeString(statusString);
}
out.write(appStatus);
if (appStatusString != null) {
out.writeString(appStatusString);
}
out.writeInt(clusterRoundTripTime);
if (m_exception != null) {
final ByteBuffer b = ByteBuffer.allocate(m_exception.getSerializedSize());
m_exception.serializeToBuffer(b);
out.write(b.array());
}
out.writeArray(results);
// DEBUG HANDLE
out.writeBoolean(this.debug != null);
if (this.debug != null) {
this.debug.writeExternal(out);
}
}
@Override
public String toString() {
Map<String, Object> m = new LinkedHashMap<String, Object>();
m.put("Status", this.status +
(this.statusString == null || this.statusString.isEmpty() ? "" : " / " + this.statusString));
m.put("Handle", this.clientHandle);
m.put("Restart Counter", this.restartCounter);
m.put("Single-Partition", this.singlepartition);
m.put("Speculative Execution", this.speculative);
m.put("Base Partition", this.basePartition);
m.put("Exception", m_exception);
if (this.clientRoundTripTime > 0) {
m.put("Client RoundTrip Time", this.clientRoundTripTime + " ms");
}
if (this.clusterRoundTripTime > 0) {
m.put("Cluster RoundTrip Time", this.clusterRoundTripTime + " ms");
}
m.put("Debug", this.debug);
Map<String, Object> inner = new LinkedHashMap<String, Object>();
for (int i = 0; i < results.length; i++) {
inner.put(String.format("[%d]", i), results[i].toString());
}
m.put("Results", inner);
return String.format("ClientResponse[#%d]\n%s", this.txn_id, StringUtil.formatMaps(m));
}
// ARIES
public void setAriesLogData(byte[] arieslogDataArray) {
arieslogData = arieslogDataArray;
}
public boolean hasAriesLogData() {
if (arieslogData == null || arieslogData.length == 0)
return false;
return true;
}
public byte[] getAriesLogData() {
if (hasAriesLogData()) {
return arieslogData;
}
return null;
}
// ----------------------------------------------------------------------------
// SPECIAL BYTEBUFFER MODIFIERS
// ----------------------------------------------------------------------------
/**
* Set the txn restart counter without deserializing it first
* @param arr
* @param flag
*/
public static void setRestartCounter(ByteBuffer b, int val) {
b.put(1, (byte)val);
}
/**
* Set the client handle without deserializing it first
* @param b
* @param handle
*/
public static void setClientHandle(ByteBuffer b, long handle) {
b.putLong(10, handle); // 1 + 1 + 8 = 10
}
/**
* Set the base partition for the client response without deserializing it
* @param arr
* @param flag
*/
public static void setBasePartition(ByteBuffer b, int basePartition) {
b.putInt(19, basePartition); // 1 + 1 + 8 + 8 + 1 = 19
}
/**
* Set the status without deserializing it first
* @param arr
* @param flag
*/
public static void setStatus(ByteBuffer b, Status status) {
b.put(24, (byte)status.ordinal()); // 1 + 1 + 8 + 8 + 1 + 1 + 4 = 24
}
// ----------------------------------------------------------------------------
}