/**
* 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 io.hawtjms.jms.message;
import io.hawtjms.jms.JmsConnection;
import io.hawtjms.jms.JmsDestination;
import io.hawtjms.jms.exceptions.JmsExceptionSupport;
import io.hawtjms.jms.meta.JmsMessageId;
import io.hawtjms.util.PropertyExpression;
import io.hawtjms.util.TypeConversionSupport;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.Callable;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;
public class JmsMessage implements javax.jms.Message {
private static final Map<String, PropertySetter> JMS_PROPERTY_SETERS =
new HashMap<String, PropertySetter>();
protected transient Callable<Void> acknowledgeCallback;
protected transient JmsConnection connection;
protected final JmsMessageFacade facade;
protected boolean readOnlyBody;
protected boolean readOnlyProperties;
public JmsMessage(JmsMessageFacade facade) {
this.facade = facade;
}
public JmsMessage copy() throws JMSException {
JmsMessage other = new JmsMessage(facade.copy());
other.copy(this);
return other;
}
protected void copy(JmsMessage other) {
this.readOnlyBody = other.readOnlyBody;
this.readOnlyProperties = other.readOnlyBody;
this.acknowledgeCallback = other.acknowledgeCallback;
this.connection = other.connection;
}
@Override
public int hashCode() {
String id = getJMSMessageID();
if (id != null) {
return id.hashCode();
} else {
return super.hashCode();
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || o.getClass() != getClass()) {
return false;
}
JmsMessage msg = (JmsMessage) o;
JmsMessageId oMsg = msg.facade.getMessageId();
JmsMessageId thisMsg = facade.getMessageId();
return thisMsg != null && oMsg != null && oMsg.equals(thisMsg);
}
@Override
public void acknowledge() throws JMSException {
if (acknowledgeCallback != null) {
try {
acknowledgeCallback.call();
} catch (Throwable e) {
throw JmsExceptionSupport.create(e);
}
}
}
@Override
public void clearBody() throws JMSException {
readOnlyBody = false;
}
public boolean isReadOnlyBody() {
return this.readOnlyBody;
}
public void setReadOnlyBody(boolean readOnlyBody) {
this.readOnlyBody = readOnlyBody;
}
public void setReadOnlyProperties(boolean readOnlyProperties) {
this.readOnlyProperties = readOnlyProperties;
}
@Override
public String getJMSMessageID() {
if (facade.getMessageId() == null) {
return null;
}
return facade.getMessageId().toString();
}
@Override
public void setJMSMessageID(String value) {
if (value != null) {
JmsMessageId id = new JmsMessageId(value);
facade.setMessageId(id);
} else {
facade.setMessageId(null);
}
}
public void setJMSMessageID(JmsMessageId messageId) {
facade.setMessageId(messageId);
}
@Override
public long getJMSTimestamp() {
return facade.getTimestamp();
}
@Override
public void setJMSTimestamp(long timestamp) {
facade.setTimestamp(timestamp);
}
@Override
public String getJMSCorrelationID() {
return facade.getCorrelationId();
}
@Override
public void setJMSCorrelationID(String correlationId) {
facade.setCorrelationId(correlationId);
}
@Override
public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
return encodeString(facade.getCorrelationId());
}
@Override
public void setJMSCorrelationIDAsBytes(byte[] correlationId) throws JMSException {
facade.setCorrelationId(decodeString(correlationId));
}
@Override
public Destination getJMSReplyTo() throws JMSException {
return facade.getReplyTo();
}
@Override
public void setJMSReplyTo(Destination destination) throws JMSException {
facade.setReplyTo(JmsMessageTransformation.transformDestination(connection, destination));
}
@Override
public Destination getJMSDestination() throws JMSException {
return facade.getDestination();
}
@Override
public void setJMSDestination(Destination destination) throws JMSException {
facade.setDestination(JmsMessageTransformation.transformDestination(connection, destination));
}
@Override
public int getJMSDeliveryMode() {
return facade.isPersistent() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT;
}
@Override
public void setJMSDeliveryMode(int mode) {
facade.setPersistent(mode == DeliveryMode.PERSISTENT);
}
@Override
public boolean getJMSRedelivered() {
return this.isRedelivered();
}
@Override
public void setJMSRedelivered(boolean redelivered) {
this.setRedelivered(redelivered);
}
@Override
public String getJMSType() {
return facade.getType();
}
@Override
public void setJMSType(String type) {
facade.setType(type);
}
@Override
public long getJMSExpiration() {
return facade.getExpiration();
}
@Override
public void setJMSExpiration(long expiration) {
facade.setExpiration(expiration);
}
@Override
public int getJMSPriority() {
return facade.getPriority();
}
@Override
public void setJMSPriority(int priority) {
byte scaled = 0;
if (priority < 0) {
scaled = 0;
} else if (priority > 9) {
scaled = 9;
} else {
scaled = (byte) priority;
}
facade.setPriority(scaled);
}
@Override
public void clearProperties() {
facade.clearProperties();
}
@Override
public boolean propertyExists(String name) throws JMSException {
try {
return (facade.propertyExists(name) || getObjectProperty(name) != null);
} catch (Exception e) {
throw JmsExceptionSupport.create(e);
}
}
/**
* Returns an unmodifiable Map containing the properties contained within the message.
*
* @return unmodifiable Map of the current properties in the message.
*
* @throws Exception if there is an error accessing the message properties.
*/
public Map<String, Object> getProperties() throws IOException {
return Collections.unmodifiableMap(facade.getProperties());
}
/**
* Allows for a direct put of an Object value into the message properties.
*
* This method bypasses the normal JMS type checking for properties being set on
* the message and should be used with great care.
*
* @param key
* the property name to use when setting the value.
* @param value
* the value to insert into the message properties.
*
* @throws IOException if an error occurs while accessing the Message properties.
*/
public void setProperty(String key, Object value) throws IOException {
this.facade.setProperty(key, value);
}
/**
* Returns the Object value referenced by the given key.
*
* @param key
* the name of the property being accessed.
*
* @return the value stored at the given location or null if non set.
*
* @throws IOException if an error occurs while accessing the Message properties.
*/
public Object getProperty(String key) throws IOException {
return this.facade.getProperty(key);
}
@SuppressWarnings("rawtypes")
@Override
public Enumeration getPropertyNames() throws JMSException {
try {
Vector<String> result = new Vector<String>(facade.getProperties().keySet());
if (getFacade().getRedeliveryCounter() != 0) {
result.add("JMSXDeliveryCount");
}
if (getFacade().getGroupId() != null) {
result.add("JMSXGroupID");
}
if (getFacade().getGroupId() != null) {
result.add("JMSXGroupSeq");
}
if (getFacade().getUserId() != null) {
result.add("JMSXUserID");
}
return result.elements();
} catch (IOException e) {
throw JmsExceptionSupport.create(e);
}
}
/**
* return all property names, including standard JMS properties and JMSX
* properties
*
* @return Enumeration of all property names on this message
* @throws JMSException
*/
@SuppressWarnings("rawtypes")
public Enumeration getAllPropertyNames() throws JMSException {
try {
Vector<String> result = new Vector<String>(facade.getProperties().keySet());
result.addAll(JMS_PROPERTY_SETERS.keySet());
return result.elements();
} catch (IOException e) {
throw JmsExceptionSupport.create(e);
}
}
interface PropertySetter {
void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException;
}
static {
JMS_PROPERTY_SETERS.put("JMSXDeliveryCount", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Integer rc = (Integer) TypeConversionSupport.convert(connection, value, Integer.class);
if (rc == null) {
throw new MessageFormatException("Property JMSXDeliveryCount cannot be set from a " + value.getClass().getName() + ".");
}
message.getFacade().setRedeliveryCounter(rc.intValue() - 1);
}
});
JMS_PROPERTY_SETERS.put("JMSXGroupID", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
String rc = (String) TypeConversionSupport.convert(connection, value, String.class);
if (rc == null) {
throw new MessageFormatException("Property JMSXGroupID cannot be set from a " + value.getClass().getName() + ".");
}
message.getFacade().setGroupId(rc);
}
});
JMS_PROPERTY_SETERS.put("JMSXGroupSeq", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Integer rc = (Integer) TypeConversionSupport.convert(connection, value, Integer.class);
if (rc == null) {
throw new MessageFormatException("Property JMSXGroupSeq cannot be set from a " + value.getClass().getName() + ".");
}
message.getFacade().setGroupSequence(rc.intValue());
}
});
JMS_PROPERTY_SETERS.put("JMSCorrelationID", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
String rc = (String) TypeConversionSupport.convert(connection, value, String.class);
if (rc == null) {
throw new MessageFormatException("Property JMSCorrelationID cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSCorrelationID(rc);
}
});
JMS_PROPERTY_SETERS.put("JMSDeliveryMode", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Integer rc = (Integer) TypeConversionSupport.convert(connection, value, Integer.class);
if (rc == null) {
Boolean bool = (Boolean) TypeConversionSupport.convert(connection, value, Boolean.class);
if (bool == null) {
throw new MessageFormatException("Property JMSDeliveryMode cannot be set from a " + value.getClass().getName() + ".");
} else {
rc = bool.booleanValue() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT;
}
}
message.setJMSDeliveryMode(rc);
}
});
JMS_PROPERTY_SETERS.put("JMSExpiration", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Long rc = (Long) TypeConversionSupport.convert(connection, value, Long.class);
if (rc == null) {
throw new MessageFormatException("Property JMSExpiration cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSExpiration(rc.longValue());
}
});
JMS_PROPERTY_SETERS.put("JMSPriority", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Integer rc = (Integer) TypeConversionSupport.convert(connection, value, Integer.class);
if (rc == null) {
throw new MessageFormatException("Property JMSPriority cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSPriority(rc.intValue());
}
});
JMS_PROPERTY_SETERS.put("JMSRedelivered", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Boolean rc = (Boolean) TypeConversionSupport.convert(connection, value, Boolean.class);
if (rc == null) {
throw new MessageFormatException("Property JMSRedelivered cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSRedelivered(rc.booleanValue());
}
});
JMS_PROPERTY_SETERS.put("JMSReplyTo", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
JmsDestination rc = (JmsDestination) TypeConversionSupport.convert(connection, value, JmsDestination.class);
if (rc == null) {
throw new MessageFormatException("Property JMSReplyTo cannot be set from a " + value.getClass().getName() + ".");
}
message.getFacade().setReplyTo(rc);
}
});
JMS_PROPERTY_SETERS.put("JMSTimestamp", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
Long rc = (Long) TypeConversionSupport.convert(connection, value, Long.class);
if (rc == null) {
throw new MessageFormatException("Property JMSTimestamp cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSTimestamp(rc.longValue());
}
});
JMS_PROPERTY_SETERS.put("JMSType", new PropertySetter() {
@Override
public void set(JmsConnection connection, JmsMessage message, Object value) throws MessageFormatException {
String rc = (String) TypeConversionSupport.convert(connection, value, String.class);
if (rc == null) {
throw new MessageFormatException("Property JMSType cannot be set from a " + value.getClass().getName() + ".");
}
message.setJMSType(rc);
}
});
}
@Override
public void setObjectProperty(String name, Object value) throws JMSException {
setObjectProperty(name, value, true);
}
public void setObjectProperty(String name, Object value, boolean checkReadOnly) throws JMSException {
if (checkReadOnly) {
checkReadOnlyProperties();
}
if (name == null || name.equals("")) {
throw new IllegalArgumentException("Property name cannot be empty or null");
}
checkValidObject(value);
PropertySetter setter = JMS_PROPERTY_SETERS.get(name);
if (setter != null && value != null) {
setter.set(connection, this, value);
} else {
try {
facade.setProperty(name, value);
} catch (Exception e) {
throw JmsExceptionSupport.create(e);
}
}
}
public void setProperties(Map<String, Object> properties) throws JMSException {
for (Iterator<Map.Entry<String, Object>> iter = properties.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, Object> entry = iter.next();
setObjectProperty(entry.getKey(), entry.getValue());
}
}
protected void checkValidObject(Object value) throws MessageFormatException {
boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long;
valid = valid || value instanceof Float || value instanceof Double || value instanceof Character || value instanceof String || value == null;
if (!valid) {
throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass());
}
}
@Override
public Object getObjectProperty(String name) throws JMSException {
if (name == null) {
throw new NullPointerException("Property name cannot be null");
}
// PropertyExpression handles converting message headers to properties.
PropertyExpression expression = new PropertyExpression(name);
return expression.evaluate(this);
}
@Override
public boolean getBooleanProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
return false;
}
Boolean rc = (Boolean) TypeConversionSupport.convert(connection, value, Boolean.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a boolean");
}
return rc.booleanValue();
}
@Override
public byte getByteProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NumberFormatException("property " + name + " was null");
}
Byte rc = (Byte) TypeConversionSupport.convert(connection, value, Byte.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a byte");
}
return rc.byteValue();
}
@Override
public short getShortProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NumberFormatException("property " + name + " was null");
}
Short rc = (Short) TypeConversionSupport.convert(connection, value, Short.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a short");
}
return rc.shortValue();
}
@Override
public int getIntProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NumberFormatException("property " + name + " was null");
}
Integer rc = (Integer) TypeConversionSupport.convert(connection, value, Integer.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as an integer");
}
return rc.intValue();
}
@Override
public long getLongProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NumberFormatException("property " + name + " was null");
}
Long rc = (Long) TypeConversionSupport.convert(connection, value, Long.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a long");
}
return rc.longValue();
}
@Override
public float getFloatProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NullPointerException("property " + name + " was null");
}
Float rc = (Float) TypeConversionSupport.convert(connection, value, Float.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a float");
}
return rc.floatValue();
}
@Override
public double getDoubleProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
throw new NullPointerException("property " + name + " was null");
}
Double rc = (Double) TypeConversionSupport.convert(connection, value, Double.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a double");
}
return rc.doubleValue();
}
@Override
public String getStringProperty(String name) throws JMSException {
Object value = getObjectProperty(name);
if (value == null) {
return null;
}
String rc = (String) TypeConversionSupport.convert(connection, value, String.class);
if (rc == null) {
throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a String");
}
return rc;
}
@Override
public void setBooleanProperty(String name, boolean value) throws JMSException {
setBooleanProperty(name, value, true);
}
public void setBooleanProperty(String name, boolean value, boolean checkReadOnly) throws JMSException {
setObjectProperty(name, Boolean.valueOf(value), checkReadOnly);
}
@Override
public void setByteProperty(String name, byte value) throws JMSException {
setObjectProperty(name, Byte.valueOf(value));
}
@Override
public void setShortProperty(String name, short value) throws JMSException {
setObjectProperty(name, Short.valueOf(value));
}
@Override
public void setIntProperty(String name, int value) throws JMSException {
setObjectProperty(name, Integer.valueOf(value));
}
@Override
public void setLongProperty(String name, long value) throws JMSException {
setObjectProperty(name, Long.valueOf(value));
}
@Override
public void setFloatProperty(String name, float value) throws JMSException {
setObjectProperty(name, new Float(value));
}
@Override
public void setDoubleProperty(String name, double value) throws JMSException {
setObjectProperty(name, new Double(value));
}
@Override
public void setStringProperty(String name, String value) throws JMSException {
setObjectProperty(name, value);
}
public Callable<Void> getAcknowledgeCallback() {
return acknowledgeCallback;
}
public void setAcknowledgeCallback(Callable<Void> acknowledgeCallback) {
this.acknowledgeCallback = acknowledgeCallback;
}
/**
* Send operation event listener. Used to get the message ready to be sent.
*
* @throws JMSException
*/
public void onSend() throws JMSException {
setReadOnlyBody(true);
setReadOnlyProperties(true);
facade.onSend();
}
public JmsConnection getConnection() {
return connection;
}
public void setConnection(JmsConnection connection) {
this.connection = connection;
}
public boolean isExpired() {
long expireTime = facade.getExpiration();
return expireTime > 0 && System.currentTimeMillis() > expireTime;
}
public void incrementRedeliveryCount() {
facade.setRedeliveryCounter(facade.getRedeliveryCounter() + 1);
}
public JmsMessageFacade getFacade() {
return this.facade;
}
public boolean isRedelivered() {
return facade.getRedeliveryCounter() > 0;
}
public void setRedelivered(boolean redelivered) {
if (redelivered) {
if (!isRedelivered()) {
facade.setRedeliveryCounter(1);
}
} else {
if (isRedelivered()) {
facade.setRedeliveryCounter(0);
}
}
}
protected static String decodeString(byte[] data) throws JMSException {
try {
if (data == null) {
return null;
}
return new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new JMSException("Invalid UTF-8 encoding: " + e.getMessage());
}
}
protected static byte[] encodeString(String data) throws JMSException {
try {
if (data == null) {
return null;
}
return data.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new JMSException("Invalid UTF-8 encoding: " + e.getMessage());
}
}
protected void checkReadOnlyProperties() throws MessageNotWriteableException {
if (readOnlyProperties) {
throw new MessageNotWriteableException("Message properties are read-only");
}
}
protected void checkReadOnlyBody() throws MessageNotWriteableException {
if (readOnlyBody) {
throw new MessageNotWriteableException("Message body is read-only");
}
}
}