/**
*
* 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.transport.jabber;
import org.activemq.io.AbstractWireFormat;
import org.activemq.io.WireFormat;
import org.activemq.io.util.ByteArray;
import org.activemq.message.ActiveMQBytesMessage;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ActiveMQObjectMessage;
import org.activemq.message.ActiveMQTextMessage;
import org.activemq.message.ConnectionInfo;
import org.activemq.message.ConsumerInfo;
import org.activemq.message.Packet;
import org.activemq.util.IdGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* A wire format which uses XMPP format of messages
*
* @version $Revision: 1.1 $
*/
public class JabberWireFormat extends AbstractWireFormat {
private static final Log log = LogFactory.getLog(JabberWireFormat.class);
private static final String NAMESPACE = "http://etherx.jabber.org/streams";
private static final String QUEUE_PREFIX = "queue:";
private static final String TOPIC_PREFIX = "topic:";
private static final String TEMP_QUEUE_PREFIX = "tempQueue:";
private static final String TEMP_TOPIC_PREFIX = "tempTopic:";
private static final QName STREAM_QNAME = new QName(NAMESPACE, "stream", "stream");
private static final QName MESSAGE_QNAME = new QName("jabber:client","message","message");
private static final QName AUTH_QNAME = new QName("jabber:iq:auth", "query", "query");
private IdGenerator idGenerator = new IdGenerator();
private String clientID = idGenerator.generateId();
private ConnectionInfo connectionInfo;
private PrintWriter writer;
private String userName;
private boolean validStream = false;
public WireFormat copy() {
return new JabberWireFormat();
}
public Packet readPacket(int firstByte, DataInput in) throws IOException {
return null; /** TODO */
}
/**
* Reads a packet from the XML stream
* @param reader
* @param returnPackets
* @throws XMLStreamException
* @throws JMSException
*/
public void readPacket(XMLStreamReader reader, List returnPackets) throws XMLStreamException, JMSException {
String sessionId = getAttributeValue("id", reader);
if (reader.next() == XMLStreamConstants.START_ELEMENT) {
QName name = reader.getName();
if (!validStream) {
if (name.equals(STREAM_QNAME)) {
validStream = true;
}
else {
String errStr = "Bad initial QName for stream. Received: " + name + " while expecting: "
+ STREAM_QNAME;
log.warn(errStr);
throw new JMSException(errStr);
}
}
else {
QName test = new QName("jabber:iq:auth", "query", "query");
if (name.equals(AUTH_QNAME)) {
if (reader.hasNext() && reader.next()==XMLStreamConstants.START_ELEMENT){
name = reader.getName();
this.userName = reader.getElementText();
//skip past the end
if (reader.hasNext()) {
reader.next();
}
if (reader.hasNext() && reader.next()==XMLStreamConstants.START_ELEMENT){
if (sessionId != null){
writer.println(" <iq id='" + sessionId +"' type='result'/>");
writer.flush();
}
}else {
//write back a request for the password
writer.println("<iq id='" + sessionId + "'");
writer.println(" type = 'result'>");
writer.println("<query xmlns='jabber:iq:auth'><username>" + this.userName + "</username><password/><digest/><resource/></query></iq>");
writer.flush();
returnPackets.add(createConnectionInfo());
returnPackets.add(createConsumerPacket());
}
}
}else if (name.equals(MESSAGE_QNAME)){
Packet pack = readMessage(reader);
if (pack != null){
returnPackets.add(pack);
}
}else {
//general catch all - just say ok ..
if (sessionId != null){
writer.println(" <iq id='" + sessionId +"' type='result'/>");
writer.flush();
}
}
}
}
}
private String getAttributeValue(String attributeName, XMLStreamReader reader) {
String result = null;
for (int i = 0;i < reader.getAttributeCount();i++) {
if (reader.getAttributeName(i).toString().equals(attributeName)) {
result = reader.getAttributeValue(i);
break;
}
}
return result;
}
public Packet writePacket(Packet packet, DataOutput out) throws IOException, JMSException {
switch (packet.getPacketType()) {
case Packet.ACTIVEMQ_MESSAGE:
writeMessage((ActiveMQMessage) packet, "", out);
break;
case Packet.ACTIVEMQ_TEXT_MESSAGE:
writeTextMessage((ActiveMQTextMessage) packet, out);
break;
case Packet.ACTIVEMQ_BYTES_MESSAGE:
writeBytesMessage((ActiveMQBytesMessage) packet, out);
break;
case Packet.ACTIVEMQ_OBJECT_MESSAGE:
writeObjectMessage((ActiveMQObjectMessage) packet, out);
break;
case Packet.ACTIVEMQ_CONNECTION_INFO:
case Packet.ACTIVEMQ_MAP_MESSAGE:
case Packet.ACTIVEMQ_STREAM_MESSAGE:
case Packet.ACTIVEMQ_BROKER_INFO:
case Packet.ACTIVEMQ_MSG_ACK:
case Packet.CONSUMER_INFO:
case Packet.DURABLE_UNSUBSCRIBE:
case Packet.INT_RESPONSE_RECEIPT_INFO:
case Packet.PRODUCER_INFO:
case Packet.RECEIPT_INFO:
case Packet.RESPONSE_RECEIPT_INFO:
case Packet.SESSION_INFO:
case Packet.TRANSACTION_INFO:
case Packet.XA_TRANSACTION_INFO:
default:
log.debug("Ignoring message type: " + packet.getPacketType() + " packet: " + packet);
}
writer.flush();
return null;
}
/**
* Can this wireformat process packets of this version
*
* @param version the version number to test
* @return true if can accept the version
*/
public boolean canProcessWireFormatVersion(int version) {
return true;
}
/**
* @return the current version of this wire format
*/
public int getCurrentWireFormatVersion() {
return 1;
}
public PrintWriter getWriter() {
return writer;
}
public void setWriter(PrintWriter writer) {
this.writer = writer;
}
// Implementation methods
//-------------------------------------------------------------------------
protected Packet createConnectionInfo() {
connectionInfo = new ConnectionInfo();
connectionInfo.setStarted(true);
connectionInfo.setClientId(this.clientID);
connectionInfo.setClientVersion("" + getCurrentWireFormatVersion());
connectionInfo.setUserName(userName);
return connectionInfo;
}
protected Packet createConsumerPacket(){
ConsumerInfo info = new ConsumerInfo();
info.setClientId(this.clientID);
info.setConsumerNo(0);
info.setStarted(true);
info.setStartTime(System.currentTimeMillis());
info.setDestination(createDestination("chat",this.userName));
return info;
}
protected void initialize() throws IOException {
//start the stream -
String hostName = InetAddress.getLocalHost().toString();
writer.println("<?xml version='1.0'?>");
writer.println("<stream:stream");
writer.println(" xmlns='jabber:client'");
writer.println(" xml:lang='en'");
writer.println(" xmlns:stream='http://etherx.jabber.org/streams'");
writer.println(" from='" + hostName + "'");
writer.println(" id='" + clientID + "'>");
writer.flush();
}
protected Packet readMessage(XMLStreamReader reader) throws XMLStreamException, JMSException {
ActiveMQTextMessage message = new ActiveMQTextMessage();
message.setJMSMessageID(idGenerator.generateId());
QName name = reader.getName();
String to = getAttributeValue("to", reader);
String type = getAttributeValue("type",reader);
if (type != null){
message.setJMSType(type);
}
if (to != null && to.length() > 0) {
message.setJMSDestination(createDestination(type,to));
}
if (this.userName != null && this.userName .length() > 0) {
message.setJMSReplyTo(createDestination("chat",this.userName));
}
while (reader.hasNext()) {
switch (reader.nextTag()) {
case XMLStreamConstants.START_ELEMENT:
if (!readElement(reader, message)) {
log.debug("Unknown element: " + reader.getName());
}
break;
case XMLStreamConstants.END_ELEMENT:
case XMLStreamConstants.END_DOCUMENT:
return message;
}
}
return message;
}
protected boolean readElement(XMLStreamReader reader, ActiveMQTextMessage message) throws JMSException, XMLStreamException {
QName name = reader.getName();
String localPart = name.getLocalPart();
if (localPart.equals("body")) {
message.setText(reader.getElementText());
return true;
}
else if (localPart.equals("thread")) {
message.setJMSCorrelationID(reader.getElementText());
return true;
}
else {
return false;
}
}
protected String readXMLAsText(XMLStreamReader reader) throws XMLStreamException {
StringBuffer buffer = new StringBuffer();
int elementCount = 0;
while (reader.hasNext()) {
switch (reader.nextTag()) {
case XMLStreamConstants.START_ELEMENT:
if (elementCount++ > 0) {
writeStartElement(reader);
}
break;
case XMLStreamConstants.CHARACTERS:
buffer.append(reader.getText());
break;
case XMLStreamConstants.END_ELEMENT:
if (--elementCount <= 0) {
return buffer.toString();
}
writeEndElement(reader);
break;
case XMLStreamConstants.END_DOCUMENT:
return buffer.toString();
}
}
return buffer.toString();
}
protected void writeStartElement(XMLStreamReader reader) {
writer.print("<");
writeQName(reader.getName());
for (int i = 0, size = reader.getNamespaceCount(); i < size; i++) {
writer.print("xmlns");
String prefix = reader.getNamespacePrefix(i);
if (prefix != null && prefix.length() > 0) {
writer.print(":");
writer.print(prefix);
}
writer.print("='");
writer.print(reader.getNamespaceURI(i));
writer.print("'");
}
for (int i = 0, size = reader.getAttributeCount(); i < size; i++) {
writer.print("xmlns");
writeQName(reader.getAttributeName(i));
writer.print("='");
writer.print(reader.getAttributeValue(i));
writer.print("'");
}
writer.println(">");
}
protected void writeEndElement(XMLStreamReader reader) {
writer.print("</");
writeQName(reader.getName());
writer.println(">");
}
protected void writeQName(QName name) {
String prefix = name.getPrefix();
if (prefix != null && prefix.length() > 0) {
writer.print(prefix);
writer.print(":");
}
writer.print(name.getLocalPart());
}
protected ActiveMQDestination createDestination(String typeName,String text) {
int type = ActiveMQDestination.ACTIVEMQ_QUEUE;
if (text.startsWith(TOPIC_PREFIX)) {
type = ActiveMQDestination.ACTIVEMQ_TOPIC;
text = text.substring(TOPIC_PREFIX.length());
}
else if (text.startsWith(QUEUE_PREFIX)) {
type = ActiveMQDestination.ACTIVEMQ_QUEUE;
text = text.substring(QUEUE_PREFIX.length());
}
else if (text.startsWith(TEMP_QUEUE_PREFIX)) {
type = ActiveMQDestination.ACTIVEMQ_TEMPORARY_QUEUE;
text = text.substring(TEMP_QUEUE_PREFIX.length());
}
else if (text.startsWith(TEMP_TOPIC_PREFIX)) {
type = ActiveMQDestination.ACTIVEMQ_TEMPORARY_TOPIC;
text = text.substring(TEMP_TOPIC_PREFIX.length());
}else {
if (typeName != null){
if (typeName.equals("groupchat")){
type = ActiveMQDestination.ACTIVEMQ_TOPIC;
}
//else default is a queue - (assume default typeName is 'chat')
}
}
text = text.trim();
if (text.length() == 0) {
return null;
}
return ActiveMQDestination.createDestination(type, text);
}
protected String toString(Destination destination) {
if (destination instanceof ActiveMQDestination) {
ActiveMQDestination activeDestination = (ActiveMQDestination) destination;
String physicalName = activeDestination.getPhysicalName();
switch (activeDestination.getDestinationType()) {
case ActiveMQDestination.ACTIVEMQ_QUEUE:
return QUEUE_PREFIX + physicalName;
case ActiveMQDestination.ACTIVEMQ_TEMPORARY_QUEUE:
return TEMP_QUEUE_PREFIX + physicalName;
case ActiveMQDestination.ACTIVEMQ_TEMPORARY_TOPIC:
return TEMP_TOPIC_PREFIX + physicalName;
}
return physicalName;
}
return destination != null ? destination.toString() : "";
}
protected void writeObjectMessage(ActiveMQObjectMessage message, DataOutput out) throws JMSException, IOException {
Serializable object = message.getObject();
String text = (object != null) ? object.toString() : "";
writeMessage(message, text, out);
}
protected void writeTextMessage(ActiveMQTextMessage message, DataOutput out) throws JMSException, IOException {
writeMessage(message, message.getText(), out);
}
protected void writeBytesMessage(ActiveMQBytesMessage message, DataOutput out) throws IOException {
ByteArray data = message.getBodyAsBytes();
String text = encodeBinary(data.getBuf(), data.getOffset(), data.getLength());
writeMessage(message, text, out);
}
protected void writeMessage(ActiveMQMessage message, String body, DataOutput out) throws IOException {
String type = getXmppType(message);
writer.print("<");
writer.print(type);
writer.print(" to='");
writer.print(toString(message.getJMSDestination()));
writer.print("' from='");
writer.print(toString(message.getJMSReplyTo()));
String messageID = message.getJMSMessageID();
if (messageID != null) {
writer.print("' id='");
writer.print(messageID);
}
Map properties = message.getProperties();
if (properties != null) {
for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (value != null) {
writer.print("' ");
writer.print(key.toString());
writer.print("='");
writer.print(value.toString());
}
}
}
writer.println("'>");
String id = message.getJMSCorrelationID();
if (id != null) {
writer.print("<thread>");
writer.print(id);
writer.print("</thread>");
}
writer.print("<body>");
writer.print(body);
writer.println("</body>");
writer.print("</");
writer.print(type);
writer.println(">");
}
protected String encodeBinary(byte[] data, int offset, int length) {
// TODO
throw new RuntimeException("Not implemented yet!");
}
protected String getXmppType(ActiveMQMessage message) {
String type = message.getJMSType();
if (type == null) {
type = "message";
}
return type;
}
}