//
// JmsaMessage.java - JMS Adapter Message
//
// A message is comprised of
//   headers
//   properties
//   body (implemented by subclasses)
//
// For received Messages, we copy to JmsaMessage fields, and keep hold of
// the original Message, so we can acknowledge it.
//
// For outbound Messages, we work with JmsaMessage fields, and we can send
// the JmsaMessage as underlying JMS providers must support foreign Messages.
//

package nyangau.jmsadapter;

import java.util.Enumeration;
import java.util.Collections;
import java.util.HashMap;
import javax.jms.*;

public class JmsaMessage implements Message
  {
//...scantConvert:2:
protected static String cantConvert(String name, Object o, String to)
  {
  return "can not convert " + name + " from " + o.getClass().getName() + " to " + to;
  }
//...e

  // Headers

//...sJMSCorrelationID:2:
protected String JMSCorrelationID;

public String getJMSCorrelationID()
  throws JMSException
  { return JMSCorrelationID; }

public void setJMSCorrelationID(String correlationID)
  throws JMSException
  { JMSCorrelationID = correlationID; }
//...e
//...sJMSCorrelationIDAsBytes:2:
// Non-portable

protected byte[] JMSCorrelationIDAsBytes; // or null

public byte[] getJMSCorrelationIDAsBytes()
  throws JMSException
  { return JMSCorrelationIDAsBytes; }

public void setJMSCorrelationIDAsBytes(byte[] correlationID)
  throws JMSException
  { JMSCorrelationIDAsBytes = correlationID.clone(); }
//...e
//...sJMSMessageID:2:
protected String JMSMessageID; // can be null

public String getJMSMessageID()
  throws JMSException
  { return JMSMessageID; }

public void setJMSMessageID(String id)
  throws JMSException
  { JMSMessageID = id; }
//...e
//...sJMSDeliveryMode:2:
protected int JMSDeliveryMode;

public int getJMSDeliveryMode()
  throws JMSException
  { return JMSDeliveryMode; }

public void setJMSDeliveryMode(int deliveryMode)
  throws JMSException
  { JMSDeliveryMode = deliveryMode; }
//...e
//...sJMSPriority:2:
protected int JMSPriority;

public int getJMSPriority()
  throws JMSException
  { return JMSPriority; }

public void setJMSPriority(int priority)
  throws JMSException
  { JMSPriority = priority; }
//...e
//...sJMSRedelivered:2:
protected boolean JMSRedelivered;

public boolean getJMSRedelivered()
  throws JMSException
  { return JMSRedelivered; }

public void setJMSRedelivered(boolean redelivered)
  throws JMSException
  { JMSRedelivered = redelivered; }
//...e
//...sJMSTimestamp:2:
protected long JMSTimestamp;

public long getJMSTimestamp()
  throws JMSException
  { return JMSTimestamp; }

public void setJMSTimestamp(long timestamp)
  throws JMSException
  { JMSTimestamp = timestamp; }
//...e
//...sJMSExpiration:2:
protected long JMSExpiration;

public long getJMSExpiration()
  throws JMSException
  { return JMSExpiration; }

public void setJMSExpiration(long expiration)
  throws JMSException
  { JMSExpiration = expiration; }
//...e
//...sJMSType:2:
protected String JMSType;

public String getJMSType()
  throws JMSException
  { return JMSType; }

public void setJMSType(String type)
  throws JMSException
  { JMSType = type; }
//...e
//...sJMSDestination:2:
protected Destination JMSDestination;

public Destination getJMSDestination()
  throws JMSException
  { return JMSDestination; }

public void setJMSDestination(Destination destination)
  throws JMSException
  { JMSDestination = destination; }
//...e
//...sJMSReplyTo:2:
protected Destination JMSReplyTo;

public Destination getJMSReplyTo()
  throws JMSException
  { return JMSReplyTo; }

public void setJMSReplyTo(Destination replyTo)
  throws JMSException
  { JMSReplyTo = replyTo; }

// To help with sending JmsaMessages to underlying JMS

protected Destination JMSReplyToSaved;

public void pushJMSReplyTo(Destination replyTo)
  {
  JMSReplyToSaved = JMSReplyTo;
  JMSReplyTo = replyTo;
  }

public void popJMSReplyTo()
  { JMSReplyTo = JMSReplyToSaved; }
//...e

  // Properties

  protected HashMap<String,Object> p;
  protected boolean pro;

//...scantConvertProperty:2:
protected static String cantConvertProperty(String name, Object o, String to)
  {
  return cantConvert("Message property " + name, o, to);
  }
//...e
//...scheckSetProperty:2:
protected void checkSetProperty(String name)
  throws MessageNotWriteableException
  {
  if ( name == null || name.length() == 0 )
    throw new IllegalArgumentException("Null or empty Message property name");
  if ( pro )
    throw new MessageNotWriteableException("Message properties read only, can't set "+name+" property");
  }
//...e

//...sBooleanProperty:2:
// from Boolean, String
 
public boolean getBooleanProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Boolean.valueOf(null).booleanValue();
  if ( o instanceof Boolean )
    return ((Boolean)o).booleanValue();
  if ( o instanceof String )
    return Boolean.valueOf((String)o).booleanValue();
  throw new MessageFormatException(cantConvertProperty(name, o, "boolean"));
  }

public void setBooleanProperty(String name, boolean value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Boolean(value));
  }
//...e
//...sByteProperty:2:
// from Byte, String

public byte getByteProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Byte.parseByte(null);
  if ( o instanceof Byte )
    return ((Byte)o).byteValue();
  if ( o instanceof String )
    return Byte.parseByte((String)o);
  throw new MessageFormatException(cantConvertProperty(name, o, "byte"));
  }

public void setByteProperty(String name, byte value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Byte(value));
  }
//...e
//...sShortProperty:2:
// from Short, String, Byte

public short getShortProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Short.parseShort(null);
  if ( o instanceof Short )
    return ((Short)o).shortValue();
  if ( o instanceof String )
    return Short.parseShort((String)o);
  if ( o instanceof Byte )
    return ((Byte)o).shortValue();
  throw new MessageFormatException(cantConvertProperty(name, o, "short"));
  }

public void setShortProperty(String name, short value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Short(value));
  }
//...e
//...sIntProperty:2:
// from Integer, String, Short, Byte

public int getIntProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Integer.parseInt(null);
  if ( o instanceof Integer )
    return ((Integer)o).intValue();
  if ( o instanceof String )
    return Integer.parseInt((String)o);
  if ( o instanceof Short )
    return ((Short)o).intValue();
  if ( o instanceof Byte )
    return ((Byte)o).intValue();
  throw new MessageFormatException(cantConvertProperty(name, o, "int"));
  }

public void setIntProperty(String name, int value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Integer(value));
  }
//...e
//...sLongProperty:2:
// from Long, String, Integer, Short, Byte

public long getLongProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Long.parseLong(null);
  if ( o instanceof Long )
    return ((Long)o).longValue();
  if ( o instanceof String )
    return Long.parseLong((String)o);
  if ( o instanceof Integer )
    return ((Integer)o).longValue();
  if ( o instanceof Short )
    return ((Short)o).longValue();
  if ( o instanceof Byte )
    return ((Byte)o).longValue();
  throw new MessageFormatException(cantConvertProperty(name, o, "long"));
  }

public void setLongProperty(String name, long value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Long(value));
  }
//...e
//...sFloatProperty:2:
// from Float, String

public float getFloatProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Float.parseFloat(null);
  if ( o instanceof Float )
    return ((Float)o).floatValue();
  if ( o instanceof String )
    return Float.parseFloat((String)o);
  throw new MessageFormatException(cantConvertProperty(name, o, "float"));
  }

public void setFloatProperty(String name, float value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Float(value));
  }
//...e
//...sDoubleProperty:2:
// from Double, String, Float

public double getDoubleProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return Double.parseDouble(null);
  if ( o instanceof Double )
    return ((Double)o).doubleValue();
  if ( o instanceof String )
    return Double.parseDouble((String)o);
  if ( o instanceof Float )
    return ((Float)o).doubleValue();
  throw new MessageFormatException(cantConvertProperty(name, o, "double"));
  }

public void setDoubleProperty(String name, double value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, new Double(value));
  }
//...e
//...sStringProperty:2:
// from String, and rest via toString()

public String getStringProperty(String name)
  throws JMSException
  {
  Object o = p.get(name);
  if ( o == null )
    return null;
  if ( o instanceof String )
    return (String) o;
  return o.toString();
  }

public void setStringProperty(String name, String value)
  throws JMSException
  {
  checkSetProperty(name);
  p.put(name, value);
  }
//...e
//...sObjectProperty:2:
public Object getObjectProperty(String name)
  throws JMSException
  { return p.get(name); }

public void setObjectProperty(String name, Object value)
  throws JMSException
  {
  checkSetProperty(name);
  if ( value instanceof Boolean ||
       value instanceof Byte    ||
       value instanceof Short   ||
       value instanceof Integer ||
       value instanceof Long    ||
       value instanceof Float   ||
       value instanceof Double  ||
       value instanceof String  )
    p.put(name, value);
  else
    throw new MessageFormatException("Property value is of invalid class: " + value.getClass().getName());
  }
//...e

//...sgetPropertyNames:2:
public Enumeration getPropertyNames()
  throws JMSException
  { return Collections.enumeration( p.keySet() ); }
//...e
//...spropertyExists:2:
public boolean propertyExists(String name)
  throws JMSException
  { return p.containsKey(name); }
//...e
//...sclearProperties:2:
public void clearProperties()
  throws JMSException
  {
  p.clear();
  pro = false;
  }
//...e
//...sclearProperty:2:
// Not a part of JMS javax.jms.Message interface
// Exists to support <propdel> in flow processor

public void clearProperty(String name)
  { p.remove(name); }
//...e

  // Body

  protected boolean bro;
//...scheckRead:2:
protected void checkRead()
  throws JMSException
  {
  if ( !bro )
    throw new MessageNotReadableException("message body not readable");
  }
//...e
//...scheckWrite:2:
protected void checkWrite()
  throws JMSException
  {
  if ( bro )
    throw new MessageNotWriteableException("message body not writeable");
  }
//...e
//...sclearBody:2:
public void clearBody()
  throws JMSException
  {
  throw new JMSException("Subclass of JmsaMessage should have implemented this method");
  }
//...e

  // Message as a whole

//...sacknowledge:2:
public void acknowledge()
  throws JMSException
  { mGot.acknowledge(); }
//...e

//...sJmsaMessage:2:
// Called by JmsaSession
public JmsaMessage()
  {
  JMSCorrelationID        = null;
  JMSCorrelationIDAsBytes = null;
  JMSMessageID            = ""; // Not valid until sent
  JMSDeliveryMode         = Message.DEFAULT_DELIVERY_MODE;
  JMSPriority             = Message.DEFAULT_PRIORITY;
  JMSRedelivered          = false;
  JMSTimestamp            = 0; // Set when message is sent
  JMSExpiration           = 0; // Set when message is sent
  JMSType                 = ""; // Set by the client
  JMSDestination          = null; // Set after message is sent
  JMSReplyTo              = null; // Client sets this
  p                       = new HashMap<String,Object>();
  pro                     = false;
  bro                     = false;
  mGot                    = null;
  }

// For newly received messages, or foreign outbound messages
public JmsaMessage(Message m)
  throws JMSException // caused by m.getJMSXxxxx()
  {
  JMSCorrelationID        = m.getJMSCorrelationID();
  try
    {
    JMSCorrelationIDAsBytes = m.getJMSCorrelationIDAsBytes();
    }
  catch ( java.lang.UnsupportedOperationException e )
    // Tibco EMS throws this
    {
    JMSCorrelationIDAsBytes = null;
    }
  JMSMessageID            = m.getJMSMessageID();
  JMSDeliveryMode         = m.getJMSDeliveryMode();
  JMSPriority             = m.getJMSPriority();
  JMSRedelivered          = m.getJMSRedelivered();
  JMSTimestamp            = m.getJMSTimestamp();
  JMSExpiration           = m.getJMSExpiration();
  JMSType                 = m.getJMSType();
  JMSDestination          = null;
  JMSReplyTo              = null;
  p                       = new HashMap<String,Object>();
  for ( Enumeration e = m.getPropertyNames(); e.hasMoreElements(); )
    {
    String pn = (String) e.nextElement();
    p.put(pn, m.getObjectProperty(pn));
    }
  pro                     = false;
  bro                     = false;
  mGot                    = m;
  }

// This is used when we need to change between JmsaXxxxMessage and
// JmsaYyyyMessage (we won't be using the JmsaXxxx message afterwards).
public JmsaMessage(JmsaMessage m)
  {
  JMSCorrelationID        = m.JMSCorrelationID;
  JMSCorrelationIDAsBytes = m.JMSCorrelationIDAsBytes;
  JMSMessageID            = m.JMSMessageID;
  JMSDeliveryMode         = m.JMSDeliveryMode;
  JMSPriority             = m.JMSPriority;
  JMSRedelivered          = m.JMSRedelivered;
  JMSTimestamp            = m.JMSTimestamp;
  JMSExpiration           = m.JMSExpiration;
  JMSType                 = m.JMSType;
  JMSDestination          = m.JMSDestination;
  JMSReplyTo              = m.JMSReplyTo;
  p                       = m.p;
  pro                     = false;
  bro                     = false;
  mGot                    = m.mGot;
  }
//...e

  protected Message mGot; // can be null

  // Handy helpers

//...sprotect:2:
// Before passing a newly received JmsaMessage to the caller,
// ensure it is not modifiable, as per JMS spec.

public void protect()
  {
  pro = true;
  bro = true;
  }
//...e

//...scopyOfMessage:2:
// Make a JmsaMessage which is a copy of another message.
// Note: The returned copy will not have JMSDestination or JMSReplyTo set up.
// Note: It will also have writeable properties.
// Note: Its body might be readable or writeble, depending on message type.

public static JmsaMessage copyOfMessage(Message m)
  throws JMSException
  {
       if ( m instanceof BytesMessage )
    return new JmsaBytesMessage((BytesMessage) m);
  else if ( m instanceof TextMessage )
    return new JmsaTextMessage((TextMessage) m);
  else if ( m instanceof MapMessage )
    return new JmsaMapMessage((MapMessage) m);
  else if ( m instanceof StreamMessage )
    return new JmsaStreamMessage((StreamMessage) m);
  else if ( m instanceof ObjectMessage )
    return new JmsaObjectMessage((ObjectMessage) m);
  else
    return new JmsaMessage(m);
  }
//...e
//...scopyOfMessageWithDestinations:2:
// Make a JmsaMessage which is a copy of another message.
// Input message is typically a JmsaMessage.
// Copy the destinations also.

public static JmsaMessage copyOfMessageWithDestinations(Message m)
  throws JMSException
  {
  JmsaMessage m2 = copyOfMessage(m);
  m2.setJMSDestination( m.getJMSDestination() );
  m2.setJMSReplyTo    ( m.getJMSReplyTo    () );
  return m2;
  }
//...e
//...scopyOfMessageWithWrappedDestinations:2:
// Make a JmsaMessage which is a copy of another message.
// The input message is typically one received from the underlying JMS.
// So we wrap its destinations with JMS Adapter ones.

public static JmsaMessage copyOfMessageWithWrappedDestinations(Message m)
  throws JMSException
  {
  JmsaMessage m2 = copyOfMessage(m);
  m2.setJMSDestination( JmsaD.wrapDestination(m.getJMSDestination()) );
  m2.setJMSReplyTo    ( JmsaD.wrapDestination(m.getJMSReplyTo    ()) );
  return m2;
  }
//...e
  }
