/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.xmpp; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import com.caucho.bam.BamError; import com.caucho.bam.stream.MessageStream; import com.caucho.vfs.ReadStream; import com.caucho.xmpp.im.ImMessage; import com.caucho.xmpp.im.ImPresence; import com.caucho.xmpp.im.ImSessionQuery; import com.caucho.xmpp.im.Text; import com.caucho.xmpp.im.XmlData; /** * Protocol handler from the TCP/XMPP stream forwarding to the broker */ public class XmppReader { private static final Logger log = Logger.getLogger(XmppReader.class.getName()); private MessageStream _toReply; private MessageStream _handler; private ReadStream _is; private XmppStreamReader _in; private XmppContext _xmppContext; private XmppMarshalFactory _marshalFactory; private String _uid; private String _address; private boolean _isFinest; XmppReader(XmppContext context, ReadStream is, XmppStreamReader in, MessageStream toReply, MessageStream handler) { _xmppContext = context; _marshalFactory = context.getMarshalFactory(); _is = is; _in = in; _toReply = toReply; _handler = handler; _isFinest = log.isLoggable(Level.FINEST); } void setHandler(MessageStream handler) { _handler = handler; } void setUid(String uid) { _uid = uid; } void setAddress(String address) { _address = address; } boolean readNext() throws IOException { XmppStreamReader in = _in; if (in == null) return false; try { int tag; while ((tag = _in.next()) > 0) { if (_isFinest) debug(_in); if (tag == XMLStreamConstants.END_ELEMENT) { if ("stream".equals(_in.getLocalName())) { if (log.isLoggable(Level.FINE)) log.fine(this + " end-stream"); } else { log.warning(this + " " + _in.getLocalName()); } return false; } if (tag == XMLStreamConstants.START_ELEMENT) { boolean valid = false; if ("iq".equals(_in.getLocalName())) valid = handleIq(); else if ("presence".equals(_in.getLocalName())) valid = handlePresence(); else if ("message".equals(_in.getLocalName())) valid = handleMessage(); else { if (log.isLoggable(Level.FINE)) log.fine(this + " " + _in.getLocalName() + " is an unknown tag"); return false; } if (! valid) return false; if (_in.available() < 1) return true; } } if (_isFinest) log.finest(this + " end of stream"); return false; } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); return false; } } /** * Processes a message * * <code><pre> * element message{xmlns="jabber:client"} { * attribute from? * & attribute to? * & attribute id? * & attribute type? * * & subject* * & body* * & thread? * & other* * } * * element body { * attribute xml:lang? * & string * } * * element subject { * attribute xml:lang? * & string * } * * element thread { * & string * } * </pre></code> */ boolean handleMessage() throws IOException, XMLStreamException { String type = _in.getAttributeValue(null, "type"); String id = _in.getAttributeValue(null, "id"); String from = _in.getAttributeValue(null, "from"); String to = _in.getAttributeValue(null, "to"); if (type == null) type = "normal"; int tag; ArrayList<Text> subjectList = null; ArrayList<Text> bodyList = null; ArrayList<Serializable> extraList = null; String thread = null; while ((tag = _in.next()) > 0 && ! (tag == XMLStreamReader.END_ELEMENT && "message".equals(_in.getLocalName()))) { if (_isFinest) debug(_in); if (tag != XMLStreamReader.START_ELEMENT) continue; if ("body".equals(_in.getLocalName()) && "jabber:client".equals(_in.getNamespaceURI())) { String lang = null; if (_in.getAttributeCount() > 0 && "lang".equals(_in.getAttributeLocalName(0))) { lang = _in.getAttributeValue(0); } tag = _in.next(); if (_isFinest) debug(_in); String body = _in.getText(); if (bodyList == null) bodyList = new ArrayList<Text>(); bodyList.add(new Text(body, lang)); expectEnd("body"); } else if ("subject".equals(_in.getLocalName()) && "jabber:client".equals(_in.getNamespaceURI())) { String lang = null; if (_in.getAttributeCount() > 0 && "lang".equals(_in.getAttributeLocalName(0))) lang = _in.getAttributeValue(0); tag = _in.next(); if (_isFinest) debug(_in); String text = _in.getText(); if (subjectList == null) subjectList = new ArrayList<Text>(); subjectList.add(new Text(text, lang)); expectEnd("subject"); } else if ("thread".equals(_in.getLocalName()) && "jabber:client".equals(_in.getNamespaceURI())) { tag = _in.next(); if (_isFinest) debug(_in); thread = _in.getText(); expectEnd("thread"); } else { String name = _in.getLocalName(); QName qName = _in.getName(); String uri = _in.getNamespaceURI(); if (extraList == null) extraList = new ArrayList<Serializable>(); XmppMarshal marshal = _marshalFactory.getUnserialize(qName); Serializable extra; if (marshal != null) extra = marshal.fromXml(_in); else extra = readAsXmlString(_in); // extraList.add(new XmlData(name, uri, data)); extraList.add(extra); } } expectEnd("message", tag); Text []subjectArray = null; if (subjectList != null) { subjectArray = new Text[subjectList.size()]; subjectList.toArray(subjectArray); } Text []bodyArray = null; if (bodyList != null) { bodyArray = new Text[bodyList.size()]; bodyList.toArray(bodyArray); } Serializable []extraArray = null; if (extraList != null) { extraArray = new Serializable[extraList.size()]; extraList.toArray(extraArray); } if (_address != null) from = _address; if (to == null) to = _uid; Serializable message; /* if (! "normal".equals(type) || subjectArray != null || bodyArray != null || thread != null || extraArray == null || extraArray.length > 1) { } else { message = extraArray[0]; } */ message = new ImMessage(to, from, type, subjectArray, bodyArray, thread, extraArray); if (_handler != null) _handler.message(to, from, message); return true; } /** * Processes a query */ boolean handleIq() throws IOException, XMLStreamException { String type = _in.getAttributeValue(null, "type"); String id = _in.getAttributeValue(null, "id"); String from = _in.getAttributeValue(null, "from"); String to = _in.getAttributeValue(null, "to"); int tag = _in.nextTag(); if (_isFinest) debug(_in); String localName = _in.getLocalName(); String uri = _in.getNamespaceURI(); QName name = _in.getName(); Serializable query = null; XmppMarshal marshal = _marshalFactory.getUnserialize(name); if (marshal != null) query = marshal.fromXml(_in); else query = readAsXmlString(_in); BamError error = null; skipToEnd("iq"); if (_address != null) from = _address; if (to == null) { to = _uid; if (query instanceof ImSessionQuery && "set".equals(type)) { long bamId = _xmppContext.addId(id); _toReply.queryResult(bamId, from, to, query); return true; } } if ("get".equals(type)) { long bamId = _xmppContext.addId(id); if (_handler != null) _handler.query(bamId, to, from, query); } /* else if ("set".equals(type)) { long bamId = _xmppContext.addId(id); if (_handler != null) _handler.querySet(bamId, to, from, query); } */ else if ("result".equals(type)) { long bamId = Long.parseLong(id); if (_handler != null) _handler.queryResult(bamId, to, from, query); } else if ("error".equals(type)) { long bamId = Long.parseLong(id); if (_handler != null) _handler.queryError(bamId, to, from, query, error); } else { if (log.isLoggable(Level.FINE)) { log.fine(this + " <" + _in.getLocalName() + " xmlns=" + _in.getNamespaceURI() + "> unknown type"); } } return true; } boolean handlePresence() throws IOException, XMLStreamException { String type = _in.getAttributeValue(null, "type"); String id = _in.getAttributeValue(null, "id"); String from = _in.getAttributeValue(null, "from"); String to = _in.getAttributeValue(null, "to"); String target = to; if (type == null) type = ""; int tag; String show = null; Text status = null; int priority = 0; ArrayList<Serializable> extraList = new ArrayList<Serializable>(); BamError error = null; while ((tag = _in.nextTag()) > 0 && ! ("presence".equals(_in.getLocalName()) && tag == XMLStreamReader.END_ELEMENT)) { if (_isFinest) debug(_in); if (tag != XMLStreamReader.START_ELEMENT) continue; if ("status".equals(_in.getLocalName())) { tag = _in.next(); if (_isFinest) debug(_in); status = new Text(_in.getText()); skipToEnd("status"); } else if ("show".equals(_in.getLocalName())) { tag = _in.next(); if (_isFinest) debug(_in); show = _in.getText(); skipToEnd("show"); } else if ("priority".equals(_in.getLocalName())) { tag = _in.next(); if (_isFinest) debug(_in); priority = Integer.parseInt(_in.getText()); skipToEnd("priority"); } else { String name = _in.getLocalName(); String uri = _in.getNamespaceURI(); String data = _in.readAsXmlString(); extraList.add(new XmlData(name, uri, data)); } } if (_isFinest) debug(_in); expectEnd("presence", tag); if (_address != null) from = _address; if (target == null) target = _uid; // XXX: need different types ImPresence presence = new ImPresence(to, from, show, status, priority, extraList); if (_handler != null) { /* if ("".equals(type) || "presence".equals(type)) _handler.presence(target, from, presence); else if ("probe".equals(type)) _handler.presenceProbe(target, from, presence); else if ("unavailable".equals(type)) _handler.presenceUnavailable(target, from, presence); else if ("subscribe".equals(type)) _handler.presenceSubscribe(target, from, presence); else if ("subscribed".equals(type)) _handler.presenceSubscribed(target, from, presence); else if ("unsubscribe".equals(type)) _handler.presenceUnsubscribe(target, from, presence); else if ("unsubscribed".equals(type)) _handler.presenceUnsubscribed(target, from, presence); else if ("error".equals(type)) _handler.presenceError(target, from, presence, error); else log.warning(this + " " + type + " is an unknown presence type"); */ } return true; } protected void skipToEnd(String tagName) throws IOException, XMLStreamException { XMLStreamReader in = _in; if (in == null) return; int tag; while ((tag = in.next()) > 0) { if (_isFinest) debug(in); if (tag == XMLStreamReader.START_ELEMENT) { } else if (tag == XMLStreamReader.END_ELEMENT) { if (tagName.equals(in.getLocalName())) return; } } } private void expectEnd(String tagName) throws IOException, XMLStreamException { expectEnd(tagName, _in.nextTag()); } private void expectEnd(String tagName, int tag) throws IOException, XMLStreamException { if (tag != XMLStreamReader.END_ELEMENT) throw new IllegalStateException("expected </" + tagName + "> at <" + _in.getLocalName() + ">"); else if (! tagName.equals(_in.getLocalName())) throw new IllegalStateException("expected </" + tagName + "> at </" + _in.getLocalName() + ">"); } private String readAsXmlString(XMLStreamReader in) throws IOException, XMLStreamException { StringBuilder sb = new StringBuilder(); int depth = 0; while (true) { if (XMLStreamReader.START_ELEMENT == in.getEventType()) { depth++; String prefix = in.getPrefix(); sb.append("<"); if (! "".equals(prefix)) { sb.append(prefix); sb.append(":"); } sb.append(in.getLocalName()); if (in.getNamespaceURI() != null) { if ("".equals(prefix)) sb.append(" xmlns"); else sb.append(" xmlns:").append(prefix); sb.append("=\""); sb.append(in.getNamespaceURI()).append("\""); } for (int i = 0; i < in.getAttributeCount(); i++) { sb.append(" "); sb.append(in.getAttributeLocalName(i)); sb.append("=\""); sb.append(in.getAttributeValue(i)); sb.append("\""); } sb.append(">"); log.finest(this + " " + sb); } else if (XMLStreamReader.END_ELEMENT == in.getEventType()) { depth--; sb.append("</"); String prefix = in.getPrefix(); if (! "".equals(prefix)) sb.append(prefix).append(":"); sb.append(in.getLocalName()); sb.append(">"); if (depth == 0) return sb.toString(); } else if (XMLStreamReader.CHARACTERS == in.getEventType()) { sb.append(in.getText()); } else { log.finer(this + " tag=" + in.getEventType()); return sb.toString(); } if (in.next() < 0) { log.finer(this + " unexpected end of file"); return sb.toString(); } } } private void debug(XMLStreamReader in) throws IOException, XMLStreamException { if (XMLStreamReader.START_ELEMENT == in.getEventType()) { StringBuilder sb = new StringBuilder(); sb.append("<").append(in.getLocalName()); if (in.getNamespaceURI() != null) sb.append("{").append(in.getNamespaceURI()).append("}"); for (int i = 0; i < in.getAttributeCount(); i++) { sb.append(" "); sb.append(in.getAttributeLocalName(i)); sb.append("='"); sb.append(in.getAttributeValue(i)); sb.append("'"); } sb.append(">"); log.finest(this + " " + sb); } else if (XMLStreamReader.END_ELEMENT == in.getEventType()) { log.finest(this + " </" + in.getLocalName() + ">"); } else if (XMLStreamReader.CHARACTERS == in.getEventType()) { String text = in.getText().trim(); if (! "".equals(text)) log.finest(this + " text='" + text + "'"); } else log.finest(this + " tag=" + in.getEventType()); } @Override public String toString() { return getClass().getSimpleName() + "[]"; } }