/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat, Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General public License for more details.
* You should have received a copy of the GNU Lesser General public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.jboss.narayana.blacktie.jatmibroker.xatmi.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.jboss.narayana.blacktie.jatmibroker.core.conf.AttributeStructure;
import org.jboss.narayana.blacktie.jatmibroker.core.conf.BufferStructure;
import org.jboss.narayana.blacktie.jatmibroker.core.conf.ConfigurationException;
import org.jboss.narayana.blacktie.jatmibroker.core.tx.TransactionException;
import org.jboss.narayana.blacktie.jatmibroker.core.tx.TransactionImpl;
import org.jboss.narayana.blacktie.jatmibroker.xatmi.Buffer;
import org.jboss.narayana.blacktie.jatmibroker.xatmi.ConnectionException;
import org.jboss.narayana.blacktie.jatmibroker.xatmi.ConnectionFactory;
/**
* This class is used to send and receive data to and from clients to services.
*
* @see X_OCTET_Impl
* @see X_C_TYPE_Impl
* @see X_COMMON_Impl
*/
public abstract class BufferImpl implements Serializable, Buffer {
/**
* A none-default id.
*/
private static final long serialVersionUID = 1L;
/**
* The agreed size of a byte.
*/
public static final int BYTE_SIZE = 1;
/**
* The agreed size of a long.
*/
public static final int LONG_SIZE = 8;
/**
* The agreed size of a int.
*/
public static final int INT_SIZE = 4;
/**
* The agreed size of a short.
*/
public static final int SHORT_SIZE = 2;
/**
* The agreed size of a float.
*/
public static final int FLOAT_SIZE = INT_SIZE;
/**
* The agreed size of a double.
*/
public static final int DOUBLE_SIZE = LONG_SIZE;
/**
* The structure of the buffer.
*/
private Map<String, Object> structure = new HashMap<String, Object>();
/**
* The supported keys.
*/
private String[] keys;
/**
* The supported types
*/
private Class[] types;
/**
* The length of each array in the buffer.
*/
private int[] lengths;
/**
* Has the buffer been deserialized.
*/
private boolean deserialized;
/**
* Is the buffer formatted.
*/
private boolean formatted;
/**
* The current read position.
*/
int currentPos = 0;
/**
* The list of types supported by this class of buffer.
*
* @see X_OCTET_Impl
* @see X_C_TYPE_Impl
* @see X_COMMON_Impl
*/
private List<Class> supportedTypes;
/**
* Does the buffer require serialization? i.e. is it not an X_OCTET
*/
private boolean requiresSerialization;
/**
* The type of the buffer? X_OCTET, X_C_TYPE, X_COMMON
*/
private String type;
/**
* The subtype
*/
private String subtype;
/**
* The raw data.
*/
private byte[] data;
/**
* The number of arrays arrays.
*/
private int[] counts;
/**
* The structure of the message.
*/
private Map<String, Class> format = new HashMap<String, Class>();
protected int len = -1;
/**
* Create a new buffer.
*
* @param type The type of the buffer
* @param subtype The subtype of the buffer
* @param requiresSerialization Is the buffer not an X_OCTET?
* @param supportedTypes The types supported by the buffer, see the individual buffers for more details
* @param properties The properties to use.
* @throws ConfigurationException
* @throws ConnectionException If the buffer is not supported.
* @see {@link X_OCTET_Impl}
* @see {@link X_C_TYPE_Impl}
* @see {@link X_COMMON_Impl}
*/
BufferImpl(String type, String subtype, boolean requiresSerialization, List<Class> supportedTypes)
throws ConfigurationException, ConnectionException {
this.type = type;
this.subtype = subtype;
this.requiresSerialization = requiresSerialization;
this.supportedTypes = supportedTypes;
if (requiresSerialization) {
Properties properties = ConnectionFactory.getConnectionFactory().getProperties();
Map<String, BufferStructure> buffers = (Map<String, BufferStructure>) properties.get("blacktie.domain.buffers");
BufferStructure buffer = buffers.get(subtype);
if (buffer == null) {
throw new ConfigurationException("Subtype was not registered: " + subtype);
}
this.len = buffer.wireSize;
String[] ids = new String[buffer.attributes.size()];
Class[] types = new Class[buffer.attributes.size()];
int[] length = new int[buffer.attributes.size()];
int[] count = new int[buffer.attributes.size()];
Iterator<AttributeStructure> iterator = buffer.attributes.iterator();
int i = 0;
while (iterator.hasNext()) {
AttributeStructure attribute = iterator.next();
ids[i] = attribute.id;
types[i] = attribute.type;
if (!supportedTypes.contains(types[i])) {
throw new ConfigurationException("Cannot use type configured in buffer " + types[i]);
}
length[i] = attribute.length;
count[i] = attribute.count;
i++;
}
format(ids, types, length, count);
} else {
this.len = 0;
format.put("X_OCTET", byte[].class);
}
}
/**
* Get the format of the message.
*
* @return The format of the message
*/
public Map<String, Class> getFormat() {
return format;
}
/**
* Format the buffer.
*
* @param keys The keys
* @param types The types
* @param lengths The lengths
* @param counts The number of each array
* @throws ConnectionException In case the buffer does not match the format.
*/
private void format(String[] keys, Class[] types, int[] lengths, int[] counts) throws ConnectionException {
structure.clear();
if (keys.length != types.length || types.length != lengths.length) {
throw new ConnectionException(ConnectionImpl.TPEINVAL, "Invalid format, each array description should be same length");
}
this.keys = keys;
this.types = types;
this.lengths = lengths;
this.counts = counts;
for (int i = 0; i < keys.length; i++) {
format.put(keys[i], types[i]);
}
formatted = true;
}
/**
* Deserialize the buffer.
*
* @param data The data to deserialize.
* @throws ConnectionException In case the data does not match the format defined.
*/
public void deserialize(byte[] data) throws ConnectionException {
currentPos = 0;
if (requiresSerialization) {
if (!deserialized && data != null) {
if (keys == null) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Message format not provided");
}
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
for (int i = 0; i < types.length; i++) {
if (!supportedTypes.contains(types[i])) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Cannot read type from buffer " + types[i]);
}
try {
if (types[i] == int.class) {
structure.put(keys[i], readInt(dis));
} else if (types[i] == short.class) {
structure.put(keys[i], readShort(dis));
} else if (types[i] == long.class) {
structure.put(keys[i], readLong(dis));
} else if (types[i] == byte.class) {
structure.put(keys[i], readByte(dis));
} else if (types[i] == float.class) {
structure.put(keys[i], readFloat(dis));
} else if (types[i] == double.class) {
structure.put(keys[i], readDouble(dis));
} else if (types[i] == int[].class) {
int[] toRead = new int[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readInt(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == short[].class) {
short[] toRead = new short[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readShort(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == long[].class) {
long[] toRead = new long[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readLong(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == byte[].class) {
byte[] toRead = new byte[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readByte(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == float[].class) {
float[] toRead = new float[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readFloat(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == double[].class) {
double[] toRead = new double[lengths[i]];
for (int j = 0; j < lengths[i]; j++) {
toRead[j] = readDouble(dis);
}
structure.put(keys[i], toRead);
} else if (types[i] == byte[][].class) {
byte[][] toRead = new byte[counts[i]][lengths[i]];
for (int k = 0; k < counts[i]; k++) {
for (int j = 0; j < lengths[i]; j++) {
toRead[k][j] = readByte(dis);
}
}
structure.put(keys[i], toRead);
} else {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Could not deserialize: " + types[i]);
}
} catch (IOException e) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Could not parse the value as: " + keys[i]
+ " was not a " + types[i] + " and even if it was an array of that type its length was not: "
+ lengths[i]);
}
}
}
} else {
this.data = data;
this.len = data.length;
}
deserialized = true;
}
/**
* Serialize the buffer.
*
* @return The byte array for sending.
* @throws ConnectionException In case the data cannot be formatted correctly
*/
public byte[] serialize() throws ConnectionException {
currentPos = 0;
byte[] toReturn = null;
if (requiresSerialization) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
for (int i = 0; i < types.length; i++) {
try {
if (types[i] == int.class) {
Integer toWrite = (Integer) structure.get(keys[i]);
if (toWrite != null) {
writeInt(dos, toWrite);
} else {
writeInt(dos, 0);
}
} else if (types[i] == short.class) {
Short toWrite = (Short) structure.get(keys[i]);
if (toWrite != null) {
writeShort(dos, toWrite);
} else {
writeShort(dos, (short) 0);
}
} else if (types[i] == long.class) {
Long toWrite = (Long) structure.get(keys[i]);
if (toWrite != null) {
writeLong(dos, toWrite);
} else {
writeLong(dos, 0);
}
} else if (types[i] == byte.class) {
Byte toWrite = (Byte) structure.get(keys[i]);
if (toWrite != null) {
writeByte(dos, toWrite);
} else {
// writeByte(dos, '\0');
writeByte(dos, (byte) 0);
}
} else if (types[i] == float.class) {
Float toWrite = (Float) structure.get(keys[i]);
if (toWrite != null) {
writeFloat(dos, toWrite);
} else {
writeFloat(dos, 0);
}
} else if (types[i] == double.class) {
Double toWrite = (Double) structure.get(keys[i]);
if (toWrite != null) {
writeDouble(dos, toWrite);
} else {
writeDouble(dos, 0);
}
} else if (types[i] == int[].class) {
int[] toWrite = (int[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < lengths[i]; j++) {
writeInt(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
writeInt(dos, 0);
}
} else if (types[i] == short[].class) {
short[] toWrite = (short[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < lengths[i]; j++) {
writeShort(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
writeShort(dos, (short) 0);
}
} else if (types[i] == long[].class) {
long[] toWrite = (long[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < lengths[i]; j++) {
writeLong(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
writeLong(dos, 0);
}
} else if (types[i] == byte[].class) {
byte[] toWrite = (byte[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < max; j++) {
writeByte(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
// writeByte(dos, '\0');
writeByte(dos, (byte) 0);
}
} else if (types[i] == float[].class) {
float[] toWrite = (float[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < lengths[i]; j++) {
writeFloat(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
writeFloat(dos, 0);
}
} else if (types[i] == double[].class) {
double[] toWrite = (double[]) structure.get(keys[i]);
int max = 0;
if (toWrite != null) {
max = Math.min(lengths[i], toWrite.length);
for (int j = 0; j < lengths[i]; j++) {
writeDouble(dos, toWrite[j]);
}
}
for (int j = max; j < lengths[i]; j++) {
writeDouble(dos, 0);
}
} else if (types[i] == byte[][].class) {
byte[][] toWrite = (byte[][]) structure.get(keys[i]);
if (toWrite != null) {
for (int k = 0; k < counts[i]; k++) {
for (int j = 0; j < lengths[i]; j++) {
writeByte(dos, toWrite[k][j]);
}
}
} else {
for (int j = 0; j < counts[i] * lengths[i]; j++) {
writeByte(dos, (byte) 0);
}
}
} else {
if (TransactionImpl.current() != null) {
try {
TransactionImpl.current().rollback_only();
} catch (TransactionException e) {
throw new ConnectionException(ConnectionImpl.TPESYSTEM,
"Could not mark transaction for rollback only");
}
}
throw new ConnectionException(ConnectionImpl.TPEOTYPE, "Could not serialize: " + types[i]);
}
} catch (IOException e) {
throw new ConnectionException(ConnectionImpl.TPEOTYPE, "Could not parse the value as: " + keys[i]
+ " was not a " + types[i] + " and even if it was an array of that type its length was not: "
+ lengths[i]);
}
}
toReturn = baos.toByteArray();
} else {
toReturn = getRawData();
}
if (toReturn == null) {
toReturn = new byte[1];
}
return toReturn;
}
/**
* Write a byte during serialization/
*
* @param dos The output stream to write to.
* @param b The byte to write.
* @throws IOException In case the output stream fails.
*/
private void writeByte(DataOutputStream dos, byte b) throws IOException {
dos.writeByte(b);
currentPos += 1;
}
/**
* Read a byte during deserialization.
*
* @param dis The input stream to read from
* @return The byte
* @throws IOException In case the stream cannot be read.
*/
private byte readByte(DataInputStream dis) throws IOException {
currentPos += 1;
byte x = dis.readByte();
ByteBuffer bbuf = ByteBuffer.allocate(BYTE_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.put(x);
//bbuf.order(ByteOrder.BIG_ENDIAN);
return bbuf.get(0);
}
private void writeLong(DataOutputStream dos, long x) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(LONG_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.putLong(x);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
//long toWrite = bbuf.getLong(0);
//bbuf.order(ByteOrder.BIG_ENDIAN);
byte[] data = new byte[8];
Arrays.fill(data, (byte)0);
System.arraycopy(bbuf.array(), 4, data, 0, 4);
//dos.write(bbuf.array(), 4, 4);
dos.write(data);
currentPos += LONG_SIZE;
}
private long readLong(DataInputStream dis) throws IOException {
currentPos += LONG_SIZE;
//long x = dis.readLong();
ByteBuffer bbuf = ByteBuffer.allocate(LONG_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
byte[] data = new byte[8];
Arrays.fill(data, (byte)0);
dis.read(data, 4, 4);
//bbuf.order(ByteOrder.BIG_ENDIAN);
bbuf.put(data);
long x = bbuf.getLong(0);
//read the next 4 bytes
dis.read(data, 0, 4);
return x;
}
private void writeInt(DataOutputStream dos, int x) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(INT_SIZE);
//bbuf.order(ByteOrder.BIG_ENDIAN);
bbuf.putInt(x);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
int toWrite = bbuf.getInt(0);
dos.writeInt(toWrite);
currentPos += INT_SIZE;
}
private int readInt(DataInputStream dis) throws IOException {
currentPos += INT_SIZE;
int x = dis.readInt();
ByteBuffer bbuf = ByteBuffer.allocate(INT_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.putInt(x);
//bbuf.order(ByteOrder.BIG_ENDIAN);
return bbuf.getInt(0);
}
private void writeShort(DataOutputStream dos, short x) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(SHORT_SIZE);
//bbuf.order(ByteOrder.BIG_ENDIAN);
bbuf.putShort(x);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
short toWrite = bbuf.getShort(0);
dos.writeShort(toWrite);
currentPos += SHORT_SIZE;
}
private short readShort(DataInputStream dis) throws IOException {
currentPos += SHORT_SIZE;
short x = dis.readShort();
ByteBuffer bbuf = ByteBuffer.allocate(SHORT_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.putShort(x);
//bbuf.order(ByteOrder.BIG_ENDIAN);
return bbuf.getShort(0);
}
private void writeFloat(DataOutputStream dos, float x) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(FLOAT_SIZE);
//bbuf.order(ByteOrder.BIG_ENDIAN);
bbuf.putFloat(x);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
float toWrite = bbuf.getFloat(0);
dos.writeFloat(toWrite);
currentPos += FLOAT_SIZE;
}
private float readFloat(DataInputStream dis) throws IOException {
currentPos += FLOAT_SIZE;
float x = dis.readFloat();
ByteBuffer bbuf = ByteBuffer.allocate(FLOAT_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.putFloat(x);
//bbuf.order(ByteOrder.BIG_ENDIAN);
return bbuf.getFloat(0);
}
private void writeDouble(DataOutputStream dos, double x) throws IOException {
ByteBuffer bbuf = ByteBuffer.allocate(DOUBLE_SIZE);
//bbuf.order(ByteOrder.BIG_ENDIAN);
bbuf.putDouble(x);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
double toWrite = bbuf.getDouble(0);
dos.writeDouble(toWrite);
currentPos += DOUBLE_SIZE;
}
private double readDouble(DataInputStream dis) throws IOException {
currentPos += DOUBLE_SIZE;
double x = dis.readDouble();
ByteBuffer bbuf = ByteBuffer.allocate(DOUBLE_SIZE);
//bbuf.order(ByteOrder.LITTLE_ENDIAN);
bbuf.putDouble(x);
//bbuf.order(ByteOrder.BIG_ENDIAN);
return bbuf.getDouble(0);
}
/**
* Get the type
*
* @return The type
*/
public String getType() {
return type;
}
/**
* Get the subtype
*
* @return The subtype
*/
public String getSubtype() {
return subtype;
}
/**
* Clear the content of the buffer
*/
public void clear() {
structure.clear();
data = null;
}
/**
* Get the value of an attribute.
*
* @param key The key
* @param type The type
* @return The value
* @throws ConnectionException In case the message is not formatted yet.
*/
protected Object getAttributeValue(String key, Class type) throws ConnectionException {
if (!formatted) {
throw new ConnectionException(ConnectionImpl.TPEPROTO, "Message not formatted");
}
int position = -1;
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(key)) {
position = i;
}
}
if (position == -1) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Key is not part of the structure: " + key);
} else if (types[position] != type) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Key is not request type, it is a: " + types[position]);
}
return structure.get(key);
}
/**
* Set the value.
*
* @param key The key to set
* @param type The type of the value.
* @param value The value to use
* @throws ConnectionException In case the message is not formatted.
*/
protected void setAttributeValue(String key, Class type, Object value) throws ConnectionException {
if (!formatted) {
throw new ConnectionException(ConnectionImpl.TPEPROTO, "Message not formatted");
}
int position = -1;
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(key)) {
position = i;
}
}
if (position == -1) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Key is not part of the structure: " + key);
} else if (types[position] != type) {
throw new ConnectionException(ConnectionImpl.TPEITYPE, "Key is not request type, it is a: " + types[position]);
}
structure.put(key, value);
}
/**
* Set the raw data, used by the X_OCTET buffer.
*
* @param bytes The data to use.
*/
protected void setRawData(byte[] bytes) {
this.data = bytes;
}
/**
* Get the raw data, used internally and by the X_OCTET buffer.
*
* @return The data.
*/
protected byte[] getRawData() {
return data;
}
public int getLen() {
return len;
}
}