org.apache.activemq.command.ActiveMQMapMessage Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.command;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.util.ByteArrayInputStream;
import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.activemq.util.MarshallingSupport;
import org.apache.activemq.wireformat.WireFormat;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* A MapMessage
object is used to send a set of name-value pairs.
* The names are String
objects, and the values are primitive
* data types in the Java programming language. The names must have a value that
* is not null, and not an empty string. The entries can be accessed
* sequentially or randomly by name. The order of the entries is undefined.
* MapMessage
inherits from the Message
interface
* and adds a message body that contains a Map.
*
* The primitive types can be read or written explicitly using methods for each
* type. They may also be read or written generically as objects. For instance,
* a call to MapMessage.setInt("foo", 6)
is equivalent to
* MapMessage.setObject("foo", new Integer(6))
. Both forms are
* provided, because the explicit form is convenient for static programming, and
* the object form is needed when types are not known at compile time.
*
* When a client receives a MapMessage
, it is in read-only mode.
* If a client attempts to write to the message at this point, a
* MessageNotWriteableException
is thrown. If
* clearBody
is called, the message can now be both read from and
* written to.
*
* MapMessage
objects support the following conversion table. The
* marked cases must be supported. The unmarked cases must throw a
* JMSException
. The String
-to-primitive
* conversions may throw a runtime exception if the primitive's
* valueOf()
method does not accept it as a valid
* String
representation of the primitive.
*
* A value written as the row type can be read as the column type.
*
*
* | | boolean byte short char int long float double String byte[] |----------------------------------------------------------------------
* |boolean | X X |byte | X X X X X |short | X X X X |char | X X |int | X X X |long | X X |float | X X X |double | X X
* |String | X X X X X X X X |byte[] | X |----------------------------------------------------------------------
* <p/>
*
*
*
*
* Attempting to read a null value as a primitive type must be treated as
* calling the primitive's corresponding valueOf(String)
* conversion method with a null value. Since char
does not
* support a String
conversion, attempting to read a null value
* as a char
must throw a NullPointerException
.
*
* @openwire:marshaller code="25"
* @see javax.jms.Session#createMapMessage()
* @see javax.jms.BytesMessage
* @see javax.jms.Message
* @see javax.jms.ObjectMessage
* @see javax.jms.StreamMessage
* @see javax.jms.TextMessage
*/
public class ActiveMQMapMessage extends ActiveMQMessage implements MapMessage {
public static final byte DATA_STRUCTURE_TYPE = CommandTypes.ACTIVEMQ_MAP_MESSAGE;
protected transient Map map = new HashMap();
@Override
protected Object readResolve() throws ObjectStreamException {
super.readResolve();
if (this.map == null) {
this.map = new HashMap();
}
return this;
}
@Override
public Message copy() {
ActiveMQMapMessage copy = new ActiveMQMapMessage();
copy(copy);
return copy;
}
private void copy(ActiveMQMapMessage copy) {
storeContent();
super.copy(copy);
}
// We only need to marshal the content if we are hitting the wire.
@Override
public void beforeMarshall(WireFormat wireFormat) throws IOException {
super.beforeMarshall(wireFormat);
storeContent();
}
@Override
public void clearUnMarshalledState() throws JMSException {
super.clearUnMarshalledState();
map.clear();
}
@Override
public void storeContentAndClear() {
storeContent();
map.clear();
}
@Override
public void storeContent() {
try {
if (getContent() == null && !map.isEmpty()) {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
OutputStream os = bytesOut;
ActiveMQConnection connection = getConnection();
if (connection != null && connection.isUseCompression()) {
compressed = true;
os = new DeflaterOutputStream(os);
}
DataOutputStream dataOut = new DataOutputStream(os);
MarshallingSupport.marshalPrimitiveMap(map, dataOut);
dataOut.close();
setContent(bytesOut.toByteSequence());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isContentMarshalled() {
return content != null || map == null || map.isEmpty();
}
/**
* Builds the message body from data
*
* @throws JMSException
* @throws IOException
*/
private void loadContent() throws JMSException {
if (getContent() != null && map.isEmpty()) {
map = deserialize(getContent());
}
}
private Map deserialize(ByteSequence content) throws JMSException {
final Map map;
try {
if (content != null) {
InputStream is = new ByteArrayInputStream(content);
if (isCompressed()) {
is = new InflaterInputStream(is);
}
DataInputStream dataIn = new DataInputStream(is);
map = MarshallingSupport.unmarshalPrimitiveMap(dataIn);
dataIn.close();
} else {
map = new HashMap<>();
}
} catch (IOException e) {
throw JMSExceptionSupport.create(e);
}
return map;
}
@Override
public byte getDataStructureType() {
return DATA_STRUCTURE_TYPE;
}
@Override
public String getJMSXMimeType() {
return "jms/map-message";
}
/**
* Clears out the message body. Clearing a message's body does not clear its
* header values or property entries.
*
* If this message body was read-only, calling this method leaves the
* message body in the same state as an empty body in a newly created
* message.
*/
@Override
public void clearBody() throws JMSException {
super.clearBody();
map.clear();
}
/**
* Returns the boolean
value with the specified name.
*
* @param name the name of the boolean
* @return the boolean
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public boolean getBoolean(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return false;
}
if (value instanceof Boolean) {
return ((Boolean)value).booleanValue();
}
if (value instanceof UTF8Buffer) {
return Boolean.valueOf(value.toString()).booleanValue();
}
if (value instanceof String) {
return Boolean.valueOf(value.toString()).booleanValue();
} else {
throw new MessageFormatException(" cannot read a boolean from " + value.getClass().getName());
}
}
/**
* Returns the byte
value with the specified name.
*
* @param name the name of the byte
* @return the byte
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public byte getByte(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
}
if (value instanceof Byte) {
return ((Byte)value).byteValue();
}
if (value instanceof UTF8Buffer) {
return Byte.valueOf(value.toString()).byteValue();
}
if (value instanceof String) {
return Byte.valueOf(value.toString()).byteValue();
} else {
throw new MessageFormatException(" cannot read a byte from " + value.getClass().getName());
}
}
/**
* Returns the short
value with the specified name.
*
* @param name the name of the short
* @return the short
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public short getShort(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
}
if (value instanceof Short) {
return ((Short)value).shortValue();
}
if (value instanceof Byte) {
return ((Byte)value).shortValue();
}
if (value instanceof UTF8Buffer) {
return Short.valueOf(value.toString()).shortValue();
}
if (value instanceof String) {
return Short.valueOf(value.toString()).shortValue();
} else {
throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
}
}
/**
* Returns the Unicode character value with the specified name.
*
* @param name the name of the Unicode character
* @return the Unicode character value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public char getChar(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
throw new NullPointerException();
} else if (value instanceof Character) {
return ((Character)value).charValue();
} else {
throw new MessageFormatException(" cannot read a char from " + value.getClass().getName());
}
}
/**
* Returns the int
value with the specified name.
*
* @param name the name of the int
* @return the int
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public int getInt(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
}
if (value instanceof Integer) {
return ((Integer)value).intValue();
}
if (value instanceof Short) {
return ((Short)value).intValue();
}
if (value instanceof Byte) {
return ((Byte)value).intValue();
}
if (value instanceof UTF8Buffer) {
return Integer.valueOf(value.toString()).intValue();
}
if (value instanceof String) {
return Integer.valueOf(value.toString()).intValue();
} else {
throw new MessageFormatException(" cannot read an int from " + value.getClass().getName());
}
}
/**
* Returns the long
value with the specified name.
*
* @param name the name of the long
* @return the long
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public long getLong(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
}
if (value instanceof Long) {
return ((Long)value).longValue();
}
if (value instanceof Integer) {
return ((Integer)value).longValue();
}
if (value instanceof Short) {
return ((Short)value).longValue();
}
if (value instanceof Byte) {
return ((Byte)value).longValue();
}
if (value instanceof UTF8Buffer) {
return Long.valueOf(value.toString()).longValue();
}
if (value instanceof String) {
return Long.valueOf(value.toString()).longValue();
} else {
throw new MessageFormatException(" cannot read a long from " + value.getClass().getName());
}
}
/**
* Returns the float
value with the specified name.
*
* @param name the name of the float
* @return the float
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public float getFloat(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
}
if (value instanceof Float) {
return ((Float)value).floatValue();
}
if (value instanceof UTF8Buffer) {
return Float.valueOf(value.toString()).floatValue();
}
if (value instanceof String) {
return Float.valueOf(value.toString()).floatValue();
} else {
throw new MessageFormatException(" cannot read a float from " + value.getClass().getName());
}
}
/**
* Returns the double
value with the specified name.
*
* @param name the name of the double
* @return the double
value with the specified name
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public double getDouble(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return 0;
} else if (value instanceof Double) {
return ((Double)value).doubleValue();
} else if (value instanceof Float) {
return ((Float)value).floatValue();
} else if (value instanceof UTF8Buffer) {
return Double.valueOf(value.toString()).doubleValue();
} else if (value instanceof String) {
return Double.valueOf(value.toString()).doubleValue();
} else {
throw new MessageFormatException("Cannot read a double from " + value.getClass().getName());
}
}
/**
* Returns the String
value with the specified name.
*
* @param name the name of the String
* @return the String
value with the specified name; if there
* is no item by this name, a null value is returned
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public String getString(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return null;
}
if (value instanceof byte[]) {
throw new MessageFormatException("Use getBytes to read a byte array");
} else {
return value.toString();
}
}
/**
* Returns the byte array value with the specified name.
*
* @param name the name of the byte array
* @return a copy of the byte array value with the specified name; if there
* is no item by this name, a null value is returned.
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
* @throws MessageFormatException if this type conversion is invalid.
*/
@Override
public byte[] getBytes(String name) throws JMSException {
initializeReading();
Object value = map.get(name);
if (value == null) {
return null;
}
if (value instanceof byte[]) {
return (byte[])value;
} else {
throw new MessageFormatException(" cannot read a byte[] from " + value.getClass().getName());
}
}
/**
* Returns the value of the object with the specified name.
*
* This method can be used to return, in objectified format, an object in
* the Java programming language ("Java object") that had been stored in the
* Map with the equivalent setObject
method call, or its
* equivalent primitive set type
method.
*
* Note that byte values are returned as byte[]
, not
* Byte[]
.
*
* @param name the name of the Java object
* @return a copy of the Java object value with the specified name, in
* objectified format (for example, if the object was set as an
* int
, an Integer
is returned); if
* there is no item by this name, a null value is returned
* @throws JMSException if the JMS provider fails to read the message due to
* some internal error.
*/
@Override
public Object getObject(String name) throws JMSException {
initializeReading();
Object result = map.get(name);
if (result instanceof UTF8Buffer) {
result = result.toString();
}
return result;
}
/**
* Returns an Enumeration
of all the names in the
* MapMessage
object.
*
* @return an enumeration of all the names in this MapMessage
* @throws JMSException
*/
@Override
public Enumeration getMapNames() throws JMSException {
initializeReading();
return Collections.enumeration(map.keySet());
}
protected void put(String name, Object value) throws JMSException {
if (name == null) {
throw new IllegalArgumentException("The name of the property cannot be null.");
}
if (name.length() == 0) {
throw new IllegalArgumentException("The name of the property cannot be an emprty string.");
}
map.put(name, value);
}
/**
* Sets a boolean
value with the specified name into the Map.
*
* @param name the name of the boolean
* @param value the boolean
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setBoolean(String name, boolean value) throws JMSException {
initializeWriting();
put(name, value ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Sets a byte
value with the specified name into the Map.
*
* @param name the name of the byte
* @param value the byte
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setByte(String name, byte value) throws JMSException {
initializeWriting();
put(name, Byte.valueOf(value));
}
/**
* Sets a short
value with the specified name into the Map.
*
* @param name the name of the short
* @param value the short
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setShort(String name, short value) throws JMSException {
initializeWriting();
put(name, Short.valueOf(value));
}
/**
* Sets a Unicode character value with the specified name into the Map.
*
* @param name the name of the Unicode character
* @param value the Unicode character value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setChar(String name, char value) throws JMSException {
initializeWriting();
put(name, Character.valueOf(value));
}
/**
* Sets an int
value with the specified name into the Map.
*
* @param name the name of the int
* @param value the int
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setInt(String name, int value) throws JMSException {
initializeWriting();
put(name, Integer.valueOf(value));
}
/**
* Sets a long
value with the specified name into the Map.
*
* @param name the name of the long
* @param value the long
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setLong(String name, long value) throws JMSException {
initializeWriting();
put(name, Long.valueOf(value));
}
/**
* Sets a float
value with the specified name into the Map.
*
* @param name the name of the float
* @param value the float
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setFloat(String name, float value) throws JMSException {
initializeWriting();
put(name, Float.valueOf(value));
}
/**
* Sets a double
value with the specified name into the Map.
*
* @param name the name of the double
* @param value the double
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setDouble(String name, double value) throws JMSException {
initializeWriting();
put(name, Double.valueOf(value));
}
/**
* Sets a String
value with the specified name into the Map.
*
* @param name the name of the String
* @param value the String
value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setString(String name, String value) throws JMSException {
initializeWriting();
put(name, value);
}
/**
* Sets a byte array value with the specified name into the Map.
*
* @param name the name of the byte array
* @param value the byte array value to set in the Map; the array is copied
* so that the value for name
will not be
* altered by future modifications
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws NullPointerException if the name is null, or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setBytes(String name, byte[] value) throws JMSException {
initializeWriting();
if (value != null) {
put(name, value);
} else {
map.remove(name);
}
}
/**
* Sets a portion of the byte array value with the specified name into the
* Map.
*
* @param name the name of the byte array
* @param value the byte array value to set in the Map
* @param offset the initial offset within the byte array
* @param length the number of bytes to use
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setBytes(String name, byte[] value, int offset, int length) throws JMSException {
initializeWriting();
byte[] data = new byte[length];
System.arraycopy(value, offset, data, 0, length);
put(name, data);
}
/**
* Sets an object value with the specified name into the Map.
*
* This method works only for the objectified primitive object types (Integer
,Double
,
* Long
...), String
objects, and byte
* arrays.
*
* @param name the name of the Java object
* @param value the Java object value to set in the Map
* @throws JMSException if the JMS provider fails to write the message due
* to some internal error.
* @throws IllegalArgumentException if the name is null or if the name is an
* empty string.
* @throws MessageFormatException if the object is invalid.
* @throws MessageNotWriteableException if the message is in read-only mode.
*/
@Override
public void setObject(String name, Object value) throws JMSException {
initializeWriting();
if (value != null) {
// byte[] not allowed on properties
if (!(value instanceof byte[])) {
checkValidObject(value);
}
put(name, value);
} else {
put(name, null);
}
}
/**
* Indicates whether an item exists in this MapMessage
* object.
*
* @param name the name of the item to test
* @return true if the item exists
* @throws JMSException if the JMS provider fails to determine if the item
* exists due to some internal error.
*/
@Override
public boolean itemExists(String name) throws JMSException {
initializeReading();
return map.containsKey(name);
}
private void initializeReading() throws JMSException {
loadContent();
}
private void initializeWriting() throws MessageNotWriteableException {
checkReadOnlyBody();
setContent(null);
}
@Override
public void compress() throws IOException {
storeContent();
super.compress();
}
@Override
public String toString() {
return super.toString() + " ActiveMQMapMessage{ " + "theTable = " + map + " }";
}
public Map getContentMap() throws JMSException {
initializeReading();
return map;
}
@Override
@SuppressWarnings("unchecked")
public boolean isBodyAssignableTo(Class c) throws JMSException {
final Map map = getContentMap();
if (map == null || map.isEmpty()) {
return true;
}
return c.isAssignableFrom(java.util.Map.class);
}
@SuppressWarnings("unchecked")
protected T doGetBody(Class asType) throws JMSException {
storeContent();
final ByteSequence content = getContent();
final Map map = content != null ? deserialize(content) : null;
//This implementation treats an empty map as not having a body so if empty
//we should return null as well
if (map != null && !map.isEmpty()) {
map.replaceAll((k, v) -> v instanceof UTF8Buffer ? v.toString() : v);
return (T) map;
} else {
return null;
}
}
}