001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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    package org.apache.activemq.transport.stomp;
018    
019    import java.io.IOException;
020    import java.io.Serializable;
021    import java.io.StringReader;
022    import java.io.StringWriter;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import javax.jms.JMSException;
027    
028    import org.apache.activemq.command.ActiveMQMapMessage;
029    import org.apache.activemq.command.ActiveMQMessage;
030    import org.apache.activemq.command.ActiveMQObjectMessage;
031    import org.springframework.beans.BeansException;
032    import org.springframework.context.ApplicationContext;
033    import org.springframework.context.ApplicationContextAware;
034    
035    import com.thoughtworks.xstream.XStream;
036    import com.thoughtworks.xstream.io.HierarchicalStreamReader;
037    import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
038    import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
039    import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
040    import com.thoughtworks.xstream.io.xml.XppReader;
041    
042    /**
043     * Frame translator implementation that uses XStream to convert messages to and
044     * from XML and JSON
045     * 
046     * @author <a href="mailto:dejan@nighttale.net">Dejan Bosanac</a>
047     */
048    public class JmsFrameTranslator extends LegacyFrameTranslator implements
049                    ApplicationContextAware {
050    
051            XStream xStream = null;
052            ApplicationContext applicationContext;
053    
054            public ActiveMQMessage convertFrame(ProtocolConverter converter,
055                            StompFrame command) throws JMSException, ProtocolException {
056                    Map headers = command.getHeaders();
057                    ActiveMQMessage msg;
058                    String transformation = (String) headers.get(Stomp.Headers.TRANSFORMATION);
059                    if (headers.containsKey(Stomp.Headers.CONTENT_LENGTH) || transformation.equals(Stomp.Transformations.JMS_BYTE.toString())) {
060                            msg = super.convertFrame(converter, command);
061                    } else {
062                            HierarchicalStreamReader in;
063    
064                            try {
065                                    String text = new String(command.getContent(), "UTF-8");
066                                    switch (Stomp.Transformations.getValue(transformation)) {
067                                    case JMS_OBJECT_XML:
068                                            in = new XppReader(new StringReader(text));
069                                            msg = createObjectMessage(in);
070                                            break;
071                                    case JMS_OBJECT_JSON:
072                                            in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
073                                            msg = createObjectMessage(in);
074                                            break;
075                                    case JMS_MAP_XML:
076                                            in = new XppReader(new StringReader(text));
077                                            msg = createMapMessage(in);
078                                            break;
079                                    case JMS_MAP_JSON:
080                                            in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
081                                            msg = createMapMessage(in);
082                                            break;
083                                    default:
084                                            throw new Exception("Unkown transformation: " + transformation);
085                                    }
086                            } catch (Throwable e) {
087                                    command.getHeaders().put(Stomp.Headers.TRANSFORMATION_ERROR, e.getMessage());
088                                    msg = super.convertFrame(converter, command);
089                            }
090                    }
091                    FrameTranslator.Helper.copyStandardHeadersFromFrameToMessage(converter, command, msg, this);
092                    return msg;
093            }
094    
095            public StompFrame convertMessage(ProtocolConverter converter,
096                            ActiveMQMessage message) throws IOException, JMSException {
097                    if (message.getDataStructureType() == ActiveMQObjectMessage.DATA_STRUCTURE_TYPE) {
098                            StompFrame command = new StompFrame();
099                            command.setAction(Stomp.Responses.MESSAGE);
100                            Map<String, String> headers = new HashMap<String, String>(25);
101                            command.setHeaders(headers);
102    
103                            FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
104                                            converter, message, command, this);
105                            ActiveMQObjectMessage msg = (ActiveMQObjectMessage) message.copy();
106                            command.setContent(marshall(msg.getObject(),
107                                            headers.get(Stomp.Headers.TRANSFORMATION))
108                                            .getBytes("UTF-8"));
109                            return command;
110    
111                    } else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) { 
112                            StompFrame command = new StompFrame();
113                            command.setAction(Stomp.Responses.MESSAGE);
114                            Map<String, String> headers = new HashMap<String, String>(25);
115                            command.setHeaders(headers);
116    
117                            FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
118                                            converter, message, command, this);
119                            ActiveMQMapMessage msg = (ActiveMQMapMessage) message.copy();
120                            command.setContent(marshall((Serializable)msg.getContentMap(),
121                                            headers.get(Stomp.Headers.TRANSFORMATION))
122                                            .getBytes("UTF-8"));
123                            return command;         
124                    } else {
125                            return super.convertMessage(converter, message);
126                    }
127            }
128    
129            /**
130             * Marshalls the Object to a string using XML or JSON encoding
131             */
132            protected String marshall(Serializable object, String transformation)
133                            throws JMSException {
134                    StringWriter buffer = new StringWriter();
135                    HierarchicalStreamWriter out;
136                    if (transformation.toLowerCase().endsWith("json")) {
137                            out = new JettisonMappedXmlDriver().createWriter(buffer);
138                    } else {
139                            out = new PrettyPrintWriter(buffer);
140                    }
141                    getXStream().marshal(object, out);
142                    return buffer.toString();
143            }
144    
145            protected ActiveMQObjectMessage createObjectMessage(HierarchicalStreamReader in) throws JMSException {
146                    ActiveMQObjectMessage objMsg = new ActiveMQObjectMessage();
147                    Object obj = getXStream().unmarshal(in);
148                    objMsg.setObject((Serializable) obj);
149                    return objMsg;
150            }
151            
152            protected ActiveMQMapMessage createMapMessage(HierarchicalStreamReader in) throws JMSException {
153                    ActiveMQMapMessage mapMsg = new ActiveMQMapMessage();
154                    Map<String, Object> map = (Map<String, Object>)getXStream().unmarshal(in);
155                    for (String key : map.keySet()) {
156                            mapMsg.setObject(key, map.get(key));
157                    }
158                    return mapMsg;
159            }
160            
161            
162    
163            // Properties
164            // -------------------------------------------------------------------------
165            public XStream getXStream() {
166                    if (xStream == null) {
167                            xStream = createXStream();
168                    }
169                    return xStream;
170            }
171    
172            public void setXStream(XStream xStream) {
173                    this.xStream = xStream;
174            }
175    
176            // Implementation methods
177            // -------------------------------------------------------------------------
178            protected XStream createXStream() {
179                    XStream xstream = null;
180                    if (applicationContext != null) {
181                            String[] names = applicationContext
182                                            .getBeanNamesForType(XStream.class);
183                            for (int i = 0; i < names.length; i++) {
184                                    String name = names[i];
185                                    xstream = (XStream) applicationContext.getBean(name);
186                                    if (xstream != null) {
187                                            break;
188                                    }
189                            }
190                    }
191    
192                    if (xstream == null) {
193                            xstream = new XStream();
194                    }
195                    return xstream;
196    
197            }
198    
199            public void setApplicationContext(ApplicationContext applicationContext)
200                            throws BeansException {
201                    this.applicationContext = applicationContext;
202            }
203    
204    }