001    /** 
002     * 
003     * Copyright 2004 Protique Ltd
004     * 
005     * Licensed under the Apache License, Version 2.0 (the "License"); 
006     * you may not use this file except in compliance with the License. 
007     * You may obtain a copy of the License at 
008     * 
009     * http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS, 
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
014     * See the License for the specific language governing permissions and 
015     * limitations under the License. 
016     * 
017     **/
018    package org.activemq.message;
019    
020    import javax.jms.JMSException;
021    import javax.jms.MessageNotWriteableException;
022    import javax.jms.TextMessage;
023    import java.io.DataInput;
024    import java.io.DataOutput;
025    import java.io.IOException;
026    import java.io.UTFDataFormatException;
027    
028    /**
029     * A <CODE>TextMessage</CODE> object is used to send a message containing a
030     * <CODE>java.lang.String</CODE>.
031     * It inherits from the <CODE>Message</CODE> interface and adds a text message
032     * body.
033     * <p/>
034     * <P>This message type can be used to transport text-based messages, including
035     * those with XML content.
036     * <p/>
037     * <P>When a client receives a <CODE>TextMessage</CODE>, it is in read-only
038     * mode. If a client attempts to write to the message at this point, a
039     * <CODE>MessageNotWriteableException</CODE> is thrown. If
040     * <CODE>clearBody</CODE> is
041     * called, the message can now be both read from and written to.
042     *
043     * @see javax.jms.Session#createTextMessage()
044     * @see javax.jms.Session#createTextMessage(String)
045     * @see javax.jms.BytesMessage
046     * @see javax.jms.MapMessage
047     * @see javax.jms.Message
048     * @see javax.jms.ObjectMessage
049     * @see javax.jms.StreamMessage
050     * @see java.lang.String
051     */
052    
053    public class ActiveMQTextMessage extends ActiveMQMessage implements TextMessage {
054        private String text;
055    
056    
057        public String toString() {
058            String payload = null;
059            try {
060                if(!isMessagePart()){
061                    payload = getText();
062                }else{
063                    payload = "fragmented message";
064                }
065            }
066            catch (JMSException e) {
067                payload = "could not read payload: " + e.toString();
068            }
069            return super.toString() + ", text = " + payload;
070        }
071    
072        /**
073         * Return the type of Packet
074         *
075         * @return integer representation of the type of Packet
076         */
077    
078        public int getPacketType() {
079            return ACTIVEMQ_TEXT_MESSAGE;
080        }
081    
082        /**
083         * @return Returns a shallow copy of the message instance
084         * @throws JMSException
085         */
086    
087        public ActiveMQMessage shallowCopy() throws JMSException {
088            ActiveMQTextMessage other = new ActiveMQTextMessage();
089            this.initializeOther(other);
090            other.text = this.text;
091            return other;
092        }
093    
094        /**
095         * @return Returns a deep copy of the message - note the header fields are only shallow copied
096         * @throws JMSException
097         */
098    
099        public ActiveMQMessage deepCopy() throws JMSException {
100            return shallowCopy();
101        }
102    
103        /**
104         * Clears out the message body. Clearing a message's body does not clear
105         * its header values or property entries.
106         * <p/>
107         * <P>If this message body was read-only, calling this method leaves
108         * the message body in the same state as an empty body in a newly
109         * created message.
110         *
111         * @throws JMSException if the JMS provider fails to clear the message
112         *                      body due to some internal error.
113         */
114    
115        public void clearBody() throws JMSException {
116            super.clearBody();
117            this.text = null;
118        }
119    
120        /**
121         * Sets the string containing this message's data.
122         *
123         * @param string the <CODE>String</CODE> containing the message's data
124         * @throws JMSException                 if the JMS provider fails to set the text due to
125         *                                      some internal error.
126         * @throws MessageNotWriteableException if the message is in read-only
127         *                                      mode.
128         */
129    
130        public void setText(String string) throws JMSException {
131            if (super.readOnlyMessage) {
132                throw new MessageNotWriteableException("The message is read only");
133            }
134            // lets flush the byte memory if available
135            clearBody();
136            this.text = string;
137        }
138    
139    
140        /**
141         * Gets the string containing this message's data.  The default
142         * value is null.
143         *
144         * @return the <CODE>String</CODE> containing the message's data
145         * @throws JMSException
146         */
147    
148        public String getText() throws JMSException {
149            if (this.text == null) {
150                try {
151                    super.buildBodyFromBytes();
152                }
153                catch (IOException ioe) {
154                    JMSException jmsEx = new JMSException("failed to build body from bytes");
155                    jmsEx.setLinkedException(ioe);
156                    throw jmsEx;
157                }
158            }
159            return this.text;
160        }
161    
162        /**
163         * Used serialize the message body to an output stream
164         *
165         * @param dataOut
166         * @throws IOException
167         */
168    
169        public void writeBody(DataOutput dataOut) throws IOException {
170            if (text != null) {
171                int strlen = text.length();
172                int utflen = 0;
173                char[] charr = new char[strlen];
174                int c, count = 0;
175    
176                text.getChars(0, strlen, charr, 0);
177    
178                for (int i = 0; i < strlen; i++) {
179                    c = charr[i];
180                    if ((c >= 0x0001) && (c <= 0x007F)) {
181                        utflen++;
182                    }
183                    else if (c > 0x07FF) {
184                        utflen += 3;
185                    }
186                    else {
187                        utflen += 2;
188                    }
189                }
190    
191                byte[] bytearr = new byte[utflen + 4];
192                bytearr[count++] = (byte) ((utflen >>> 24) & 0xFF);
193                bytearr[count++] = (byte) ((utflen >>> 16) & 0xFF);
194                bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
195                bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
196                for (int i = 0; i < strlen; i++) {
197                    c = charr[i];
198                    if ((c >= 0x0001) && (c <= 0x007F)) {
199                        bytearr[count++] = (byte) c;
200                    }
201                    else if (c > 0x07FF) {
202                        bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
203                        bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
204                        bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
205                    }
206                    else {
207                        bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
208                        bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
209                    }
210                }
211                dataOut.write(bytearr);
212    
213            }
214            else {
215                dataOut.writeInt(-1);
216            }
217        }
218    
219        /**
220         * Used to help build the body from an input stream
221         *
222         * @param dataIn
223         * @throws IOException
224         */
225    
226        public void readBody(DataInput dataIn) throws IOException {
227            int utflen = dataIn.readInt();
228            if (utflen > -1) {
229                StringBuffer str = new StringBuffer(utflen);
230                byte bytearr[] = new byte[utflen];
231                int c, char2, char3;
232                int count = 0;
233    
234                dataIn.readFully(bytearr, 0, utflen);
235    
236                while (count < utflen) {
237                    c = bytearr[count] & 0xff;
238                    switch (c >> 4) {
239                        case 0:
240                        case 1:
241                        case 2:
242                        case 3:
243                        case 4:
244                        case 5:
245                        case 6:
246                        case 7:
247                            /* 0xxxxxxx */
248                            count++;
249                            str.append((char) c);
250                            break;
251                        case 12:
252                        case 13:
253                            /* 110x xxxx 10xx xxxx */
254                            count += 2;
255                            if (count > utflen) {
256                                throw new UTFDataFormatException();
257                            }
258                            char2 = bytearr[count - 1];
259                            if ((char2 & 0xC0) != 0x80) {
260                                throw new UTFDataFormatException();
261                            }
262                            str.append((char) (((c & 0x1F) << 6) | (char2 & 0x3F)));
263                            break;
264                        case 14:
265                            /* 1110 xxxx 10xx xxxx 10xx xxxx */
266                            count += 3;
267                            if (count > utflen) {
268                                throw new UTFDataFormatException();
269                            }
270                            char2 = bytearr[count - 2];
271                            char3 = bytearr[count - 1];
272                            if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
273                                throw new UTFDataFormatException();
274                            }
275                            str.append((char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)));
276                            break;
277                        default :
278                            /* 10xx xxxx, 1111 xxxx */
279                            throw new UTFDataFormatException();
280                    }
281                }
282                // The number of chars produced may be less than utflen
283                this.text = new String(str);
284            }
285        }
286        
287        /**
288         * dumps the text body as UTF-8
289         * @param dataOut
290         * @throws IOException
291         */
292        public void writeText(DataOutput dataOut) throws IOException {
293            String theText = text != null ? text : "";
294            dataOut.writeUTF(theText);
295        }
296        
297        /**
298         * read the text as UTF-8
299         * @param dataIn
300         * @throws IOException
301         */
302        public void readText(DataInput dataIn) throws IOException{
303            this.text = dataIn.readUTF();
304        }
305    }