/**
*
* Copyright 2004 Protique Ltd
*
* 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 org.activemq.io.impl;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.io.WireFormat;
import org.activemq.message.CachedValue;
import org.activemq.message.Packet;
import org.activemq.message.util.WireByteArrayInputStream;
import org.activemq.message.util.WireByteArrayOutputStream;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedShort;
/**
* This is a stateful AbstractDefaultWireFormat which implements value caching. Not optimial for use by
* many conncurrent threads. One DefaultWireFormat is typically allocated per client connection.
*
* @version $Revision: 1.1.1.1 $
*/
public class DefaultWireFormat extends AbstractDefaultWireFormat implements Serializable {
private static final int MAX_CACHE_SIZE = Short.MAX_VALUE/2; //needs to be a lot less than Short.MAX_VALUE
private static final Log log = LogFactory.getLog(DefaultWireFormat.class);
static final short NULL_VALUE = -1;
static final short CLEAR_CACHE = -2;
//
// Fields used during a write.
//
protected transient final Object writeMutex = new Object();
protected transient WireByteArrayOutputStream internalBytesOut;
protected transient DataOutputStream internalDataOut;
protected transient WireByteArrayOutputStream cachedBytesOut;
protected transient DataOutputStream cachedDataOut;
private Map writeValueCache = new HashMap();
protected transient SynchronizedShort cachedKeyGenerator;
//
// Fields used during a read.
//
protected transient final Object readMutex = new Object();
protected transient WireByteArrayInputStream internalBytesIn;
protected transient DataInputStream internalDataIn;
private Map readValueCache = new HashMap();
/**
* Default Constructor
*/
public DefaultWireFormat() {
internalBytesOut = new WireByteArrayOutputStream();
internalDataOut = new DataOutputStream(internalBytesOut);
internalBytesIn = new WireByteArrayInputStream();
internalDataIn = new DataInputStream(internalBytesIn);
this.currentWireFormatVersion = WIRE_FORMAT_VERSION;
this.cachedKeyGenerator = new SynchronizedShort((short) 0);
this.cachedBytesOut = new WireByteArrayOutputStream();
this.cachedDataOut = new DataOutputStream(cachedBytesOut);
}
/**
* @return new WireFormat
*/
public WireFormat copy() {
return new DefaultWireFormat();
}
private Object readResolve() throws ObjectStreamException {
return new DefaultWireFormat();
}
public Packet writePacket(Packet packet, DataOutput dataOut) throws IOException {
PacketWriter writer = getWriter(packet);
if (writer != null) {
synchronized (writeMutex) {
internalBytesOut.reset();
writer.writePacket(packet, internalDataOut);
internalDataOut.flush();
//reuse the byte buffer in the ByteArrayOutputStream
byte[] data = internalBytesOut.getData();
int count = internalBytesOut.size();
dataOut.writeByte(packet.getPacketType());
dataOut.writeInt(count);
//byte[] data = internalBytesOut.toByteArray();
//int count = data.length;
//dataOut.writeInt(count);
packet.setMemoryUsage(count);
dataOut.write(data, 0, count);
}
}
return null;
}
protected synchronized final Packet readPacket(DataInput dataIn, PacketReader reader) throws IOException {
synchronized (readMutex) {
Packet packet = reader.createPacket();
int length = dataIn.readInt();
packet.setMemoryUsage(length);
byte[] data = new byte[length];
dataIn.readFully(data);
//then splat into the internal datainput
internalBytesIn.restart(data);
reader.buildPacket(packet, internalDataIn);
return packet;
}
}
/**
* A helper method which converts a packet into a byte array Overrides the WireFormat to make use of the internal
* BytesOutputStream
*
* @param packet
* @return a byte array representing the packet using some wire protocol
* @throws IOException
*/
public byte[] toBytes(Packet packet) throws IOException {
byte[] data = null;
PacketWriter writer = getWriter(packet);
if (writer != null) {
synchronized (writeMutex) {
internalBytesOut.reset();
internalDataOut.writeByte(packet.getPacketType());
internalDataOut.writeInt(-1);//the length
writer.writePacket(packet, internalDataOut);
internalDataOut.flush();
data = internalBytesOut.toByteArray();
}
// lets subtract the header offset from the length
int length = data.length - 5;
packet.setMemoryUsage(length);
//write in the length to the data
data[1] = (byte) ((length >>> 24) & 0xFF);
data[2] = (byte) ((length >>> 16) & 0xFF);
data[3] = (byte) ((length >>> 8) & 0xFF);
data[4] = (byte) ((length >>> 0) & 0xFF);
}
return data;
}
///////////////////////////////////////////////////////////////
//
// Methods to handle cached values
//
///////////////////////////////////////////////////////////////
public Object getValueFromReadCache(short key){
return readValueCache.get(new Short(key));
}
protected short getWriteCachedKey(Object key) throws IOException{
if (key != null){
Short result = null;
result = (Short)writeValueCache.get(key);
if (result == null){
result = new Short(cachedKeyGenerator.increment());
writeValueCache.put(key,result);
updateCachedValue(result.shortValue(),key);
}
return result.shortValue();
}
return DefaultWireFormat.NULL_VALUE;
}
protected void validateWriteCache() throws IOException {
if (cachingEnabled) {
if (writeValueCache.size() >= MAX_CACHE_SIZE) {
writeValueCache.clear();
cachedKeyGenerator.set((short) 0);
updateCachedValue((short) -1, null);// send update to peer to
// clear the peer cache
}
}
}
protected void handleCachedValue(CachedValue cv) {
if (cv != null) {
if (cv.getId() == CLEAR_CACHE) {
// clear the cache
readValueCache.clear();
} else if (cv.getId() != NULL_VALUE) {
readValueCache.put(new Short(cv.getId()), cv.getValue());
}
}
}
private synchronized void updateCachedValue(short key, Object value) throws IOException {
if (cachedValueWriter == null) {
cachedValueWriter = new CachedValueWriter();
}
CachedValue cv = new CachedValue();
cv.setId(key);
cv.setValue(value);
cachedBytesOut.reset();
cachedValueWriter.writePacket(cv, cachedDataOut);
cachedDataOut.flush();
byte[] data = cachedBytesOut.getData();
int count = cachedBytesOut.size();
getTransportDataOut().writeByte(cv.getPacketType());
getTransportDataOut().writeInt(count);
getTransportDataOut().write(data, 0, count);
}
}