/** 
 * 
 * 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.message;


import javax.jms.JMSException;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;
import javax.jms.ObjectMessage;
import org.activemq.util.SerializationHelper;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

/**
 * An <CODE>ObjectMessage</CODE> object is used to send a message that contains
 * a serializable object in the Java programming language ("Java object").
 * It inherits from the <CODE>Message</CODE> interface and adds a body
 * containing a single reference to an object. Only <CODE>Serializable</CODE>
 * Java objects can be used.
 * <p/>
 * <P>If a collection of Java objects must be sent, one of the
 * <CODE>Collection</CODE> classes provided since JDK 1.2 can be used.
 * <p/>
 * <P>When a client receives an <CODE>ObjectMessage</CODE>, it is in read-only
 * mode. If a client attempts to write to the message at this point, a
 * <CODE>MessageNotWriteableException</CODE> is thrown. If
 * <CODE>clearBody</CODE> is called, the message can now be both read from and
 * written to.
 *
 * @see javax.jms.Session#createObjectMessage()
 * @see javax.jms.Session#createObjectMessage(Serializable)
 * @see javax.jms.BytesMessage
 * @see javax.jms.MapMessage
 * @see javax.jms.Message
 * @see javax.jms.StreamMessage
 * @see javax.jms.TextMessage
 */

public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMessage {

    private Serializable object;


    /**
     * Return the type of Packet
     *
     * @return integer representation of the type of Packet
     */

    public int getPacketType() {
        return ACTIVEMQ_OBJECT_MESSAGE;
    }

    /**
     * @return Returns a shallow copy of the message instance
     * @throws JMSException
     */

    public ActiveMQMessage shallowCopy() throws JMSException {
        ActiveMQObjectMessage other = new ActiveMQObjectMessage();
        this.initializeOther(other);
        other.object = this.object;
        return other;
    }

    /**
     * @return Returns a deep copy of the message - note the header fields are only shallow copied
     * @throws JMSException
     */

    public ActiveMQMessage deepCopy() throws JMSException {
        return shallowCopy();
    }

    /**
     * Clears out the message body. Clearing a message's body does not clear
     * its header values or property entries.
     * <p/>
     * <P>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.
     *
     * @throws JMSException if the JMS provider fails to clear the message
     *                      body due to some internal error.
     */

    public void clearBody() throws JMSException {
        super.clearBody();
        this.object = null;
    }


    /**
     * Sets the serializable object containing this message's data.
     * It is important to note that an <CODE>ObjectMessage</CODE>
     * contains a snapshot of the object at the time <CODE>setObject()</CODE>
     * is called; subsequent modifications of the object will have no
     * effect on the <CODE>ObjectMessage</CODE> body.
     *
     * @param newObject the message's data
     * @throws JMSException if the JMS provider fails to set the object
     *                      due to some internal error.
     * @throws javax.jms.MessageFormatException
     *                      if object serialization fails.
     * @throws javax.jms.MessageNotWriteableException
     *                      if the message is in read-only
     *                      mode.
     */

    public void setObject(Serializable newObject) throws JMSException {
        if (super.readOnlyMessage) {
            throw new MessageNotWriteableException("The message is read-only");
        }
        try {
            setBodyAsBytes(null);
            this.object = newObject;
            getBodyAsBytes(); // This serializes the object.
		} catch (IOException e) {
			throw (MessageFormatException)new MessageFormatException("Could not serialize the object: "+e.getMessage()).initCause(e);
		}
    }

    void setObjectPayload(Object newObject) {
        this.object = (Serializable) newObject;
    }


    /**
     * Gets the serializable object containing this message's data. The
     * default value is null.
     *
     * @return the serializable object containing this message's data
     * @throws JMSException
     */

    public Serializable getObject() throws JMSException {
        if (this.object == null) {
            try {
                super.buildBodyFromBytes();
            }
            catch (IOException ioe) {
                throw createFailedToBuildBodyException(ioe);
            }
        }
        return this.object;
    }

    /**
     * Prepare a message body for delivery
     * @throws JMSException
     */
    public void prepareMessageBody() throws JMSException{
        try {
            convertBodyToBytes();
            this.object = null;
        }
        catch (IOException ioe) {
            throw createFailedToBuildBodyException(ioe);
        }
    }

    /**
     * Used serialize the message body to an output stream
     *
     * @param dataOut
     * @throws IOException
     */

    public void writeBody(DataOutput dataOut) throws IOException {
        SerializationHelper.writeObject((OutputStream)dataOut,this.object);
    }

    /**
     * Used to help build the body from an input stream
     *
     * @param dataIn
     * @throws IOException
     */

    public void readBody(DataInput dataIn) throws IOException {
        try {
            this.object = (Serializable) SerializationHelper.readObject((InputStream)dataIn);
        }
        catch (ClassNotFoundException ex) {
            throw new IOException(ex.getMessage());
        }
    }

    public String toString() {
        String objValue = "null";
        try {
            if (!isMessagePart()){
                Object obj = getObject();
                if (obj != null){
                    objValue = obj.toString();
                }
            }else {
                objValue = "message part";
            }
        }
        catch (JMSException e) {
            object = e;
        }
        return super.toString() + " ActiveMQObjectMessage{ " +
                "object = " + objValue +
                " }";
    }

    protected JMSException createFailedToBuildBodyException(IOException ioe) {
        JMSException jmsEx = new JMSException("Failed to build body from bytes. Reason: " + ioe);
        jmsEx.setLinkedException(ioe);
        return jmsEx;
    }
}
