diff --git a/pom.xml b/pom.xml
index 7c14830..e7eb452 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,10 @@
 	<scm>
 		<url>http://svn.jabsorb.org/svn/jabsorb</url>
 	</scm>
+	<build>
+		<sourceDirectory>src</sourceDirectory>
+		<testSourceDirectory>test/src</testSourceDirectory>
+	</build>
 	<dependencies>
 		<!-- Runtime dependencies -->
 		<dependency>
@@ -77,7 +81,19 @@
 				</exclusion>
 			</exclusions>
 		</dependency>
-
+		
+		<!-- Smack XMPP library for use with JSON-RPC over XMPP -->
+		<dependency>
+			<groupId>jivesoftware</groupId>
+			<artifactId>smack</artifactId>
+			<version>3.0.4</version>
+		</dependency>
+		<dependency>
+			<groupId>jivesoftware</groupId>
+			<artifactId>smackx</artifactId>
+			<version>3.0.4</version>
+		</dependency>
+		 
 		<!-- Apache derby embedded database used by the project metrics demo in jabsorb war-->
 		<dependency>
 			<groupId>org.apache.derby</groupId>
diff --git a/src/org/jabsorb/JSONRPCXMPPConnection.java b/src/org/jabsorb/JSONRPCXMPPConnection.java
new file mode 100644
index 0000000..3112faf
--- /dev/null
+++ b/src/org/jabsorb/JSONRPCXMPPConnection.java
@@ -0,0 +1,146 @@
+/**
+ *
+ */
+package org.jabsorb;
+
+import org.jabsorb.serializer.response.results.JSONRPCResult;
+import org.jabsorb.util.XMPPConnectionManager;
+import org.jabsorb.util.XMPPURI;
+import org.jivesoftware.smack.Chat;
+import org.jivesoftware.smack.ChatManagerListener;
+import org.jivesoftware.smack.MessageListener;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smackx.muc.MultiUserChat;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author matthijs
+ */
+public class JSONRPCXMPPConnection {
+	private static final Logger log = LoggerFactory.getLogger(JSONRPCXMPPConnection.class);
+
+	private final XMPPConnection xmppConnection;
+	private final XMPPURI uri;
+
+	public JSONRPCXMPPConnection(final String uri) throws XMPPException {
+		this(new XMPPURI(uri));
+	}
+
+	public JSONRPCXMPPConnection(final XMPPURI uri) throws XMPPException {
+		this.uri = uri;
+		this.xmppConnection = XMPPConnectionManager.getInstance().getXMPPConnection(uri);
+		initConnection();
+	}
+
+	/**
+	 * Initializes the connection. Adds a MessageListener to process incoming
+	 * messages. When overriding this method, make sure to call
+	 * {@link #initConnection(String, String)}
+	 * @throws XMPPException
+	 */
+	protected void initConnection() throws XMPPException {
+		// listen to a room only?
+		if (uri.getRoom() != null) {
+			final MultiUserChat muc = new MultiUserChat(xmppConnection, uri.getRoom());
+
+			final String nickname = uri.getUser().replaceFirst("@.*", "");
+			try {
+				// attemp to join
+				muc.join(nickname, uri.getRoomPassword());
+			} catch (final XMPPException xmppException) {
+				if (XMPPError.Condition.item_not_found.toString().equals(xmppException.getXMPPError().getCondition())) {
+					// attemt to create
+					muc.create(nickname);
+
+				}
+			}
+
+			final PacketListener packetListener = new PacketListener() {
+				@Override
+				public void processPacket(final Packet packet) {
+					if (packet instanceof Message) {
+						final JSONRPCResult result = JSONRPCXMPPConnection.this.processMessage(muc, (Message) packet);
+						if (result != null) {
+							try {
+								muc.sendMessage(result.toString());
+							} catch (final XMPPException e) {
+								log.error("Error sending JSONResult message", e);
+							}
+						}
+					}
+				}
+			};
+			muc.addMessageListener(packetListener);
+		} else {
+			final MessageListener messageListener = new MessageListener() {
+				@Override
+				public void processMessage(final Chat chat, final Message message) {
+					final JSONRPCResult result = JSONRPCXMPPConnection.this.processMessage(chat, message);
+					if (result != null) {
+						try {
+							chat.sendMessage(result.toString());
+						} catch (final XMPPException e) {
+							log.error("Error sending JSONResult message", e);
+						}
+					}
+				}
+			};
+
+			xmppConnection.getChatManager().addChatListener(new ChatManagerListener() {
+				@Override
+				public void chatCreated(final Chat chat, final boolean local) {
+					// only when this is not a local chat
+					// listen to one recipient only?
+					if (!local && (uri.getRecipient() == null || chat.getParticipant().equals(uri.getRecipient()))) {
+						chat.addMessageListener(messageListener);
+					}
+				}
+			});
+		}
+	}
+
+	/**
+	 * @param context
+	 * @param message
+	 * @return the JSONRPCResult, or null when this message could not be processed
+	 */
+	protected JSONRPCResult processMessage(final Object context, final Message message) {
+		final String receiveString = message.getBody();
+		log.debug("receive: " + receiveString);
+
+		JSONObject jsonRequest;
+		try {
+			jsonRequest = new JSONObject(receiveString);
+		} catch (final JSONException e) {
+			// okay
+			log.debug("Ignoring non-JSON message");
+			return null;
+		}
+
+		if (!jsonRequest.has("method")) {
+			log.debug("Ignoring JSON message without 'method' parameter");
+			return null;
+		}
+
+		final JSONRPCBridge bridge = JSONRPCBridge.getGlobalBridge();
+		final JSONRPCResult jsonResult = bridge.call(new Object[] { context, message }, jsonRequest);
+
+		return jsonResult;
+	}
+
+	/**
+	 * Gets the underlying XMPPConnection
+	 * @return the xmppConnection
+	 */
+	public XMPPConnection getXmppConnection() {
+		return xmppConnection;
+	}
+}
diff --git a/src/org/jabsorb/client/XMPPChatSession.java b/src/org/jabsorb/client/XMPPChatSession.java
new file mode 100644
index 0000000..f9696cd
--- /dev/null
+++ b/src/org/jabsorb/client/XMPPChatSession.java
@@ -0,0 +1,54 @@
+/**
+ *
+ */
+package org.jabsorb.client;
+
+import org.jabsorb.util.XMPPURI;
+import org.jivesoftware.smack.Chat;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author matthijs
+ */
+public class XMPPChatSession extends XMPPSession implements Session {
+	private static final Logger log = LoggerFactory.getLogger(XMPPChatSession.class);
+
+	private final Chat chat;
+	private final PacketCollector collector;
+
+	/**
+	 * Opens and connects a new XMPPChatSession.
+	 * @throws XMPPException
+	 */
+	public XMPPChatSession(final XMPPURI uri) throws XMPPException {
+		super(uri);
+		this.chat = getXmppConnection().getChatManager().createChat(uri.getRecipient(), null);
+		this.collector = chat.createCollector();
+
+		log.debug("Created chat with recipient " + uri.getRecipient());
+	}
+
+	@Override
+	protected String nextMessage(final long timeout) {
+		final Packet packet = collector.nextResult(timeout);
+
+		return (packet != null ? ((Message) packet).getBody() : null);
+	}
+
+	@Override
+	protected void sendMessage(final String message) throws XMPPException {
+		chat.sendMessage(message);
+	}
+
+	/**
+	 * @return the chat
+	 */
+	public Chat getChat() {
+		return chat;
+	}
+}
diff --git a/src/org/jabsorb/client/XMPPMultiUserChatSession.java b/src/org/jabsorb/client/XMPPMultiUserChatSession.java
new file mode 100644
index 0000000..87c151f
--- /dev/null
+++ b/src/org/jabsorb/client/XMPPMultiUserChatSession.java
@@ -0,0 +1,72 @@
+/**
+ *
+ */
+package org.jabsorb.client;
+
+import org.jabsorb.util.XMPPURI;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smackx.muc.MultiUserChat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author matthijs
+ * TODO: timeout, like XMPPChatSession
+ * TODO: base class for common stuff betwee XMPPChatSession and XMPPMultiUserChatSession
+ */
+public class XMPPMultiUserChatSession extends XMPPSession {
+	/**
+	 * The logger for this class
+	 */
+	private static final Logger log = LoggerFactory.getLogger(XMPPMultiUserChatSession.class);
+
+	private final MultiUserChat multiUserChat;
+	/**
+	 * Opens and connects a new XMPPChatSession.
+	 * @throws XMPPException
+	 */
+	public XMPPMultiUserChatSession(final XMPPURI uri) throws XMPPException {
+		super(uri);
+
+		final MultiUserChat muc = new MultiUserChat(getXmppConnection(), uri.getRoom());
+		// XXX: Should MultiUserChat.getHostedRooms / getRoomInfo be used?
+
+		// XXX: Nickname in URI?
+		final String nickname = uri.getUser().replaceFirst("@.*", "");
+		try {
+			// attemp to join
+			muc.join(nickname, uri.getRoomPassword());
+
+			log.debug("Joined room " + uri.getRoom() + " with nickname " + nickname);
+		} catch (final XMPPException xmppException) {
+			if (XMPPError.Condition.item_not_found.toString().equals(xmppException.getXMPPError().getCondition())) {
+				// attemt to create
+				muc.create(nickname);
+
+				log.debug("Created room " + uri.getRoom() + " with nickname " + nickname);
+				// configure the room?
+			}
+		}
+		this.multiUserChat = muc;
+	}
+
+	@Override
+	protected String nextMessage(final long timeout) {
+		final Message message = multiUserChat.nextMessage(timeout);
+		return (message != null ? message.getBody() : null);
+	}
+
+	@Override
+	protected void sendMessage(final String message) throws XMPPException {
+		multiUserChat.sendMessage(message);
+	}
+
+	/**
+	 * @return the multiUserChat
+	 */
+	public MultiUserChat getMultiUserChat() {
+		return multiUserChat;
+	}
+}
diff --git a/src/org/jabsorb/client/XMPPSession.java b/src/org/jabsorb/client/XMPPSession.java
new file mode 100644
index 0000000..73acebc
--- /dev/null
+++ b/src/org/jabsorb/client/XMPPSession.java
@@ -0,0 +1,103 @@
+package org.jabsorb.client;
+
+import java.util.UUID;
+
+import org.jabsorb.util.XMPPConnectionManager;
+import org.jabsorb.util.XMPPURI;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class XMPPSession implements Session {
+	private static final Logger log = LoggerFactory.getLogger(XMPPSession.class);
+
+	private final XMPPConnection xmppConnection;
+	private long timeout = 60000;
+
+	public XMPPSession(final XMPPURI uri) throws XMPPException {
+		this.xmppConnection = XMPPConnectionManager.getInstance().getXMPPConnection(uri);
+	}
+
+	@Override
+	public JSONObject sendAndReceive(final JSONObject request) {
+		final String messageId;
+		try {
+			if (request.has("id")) {
+				messageId = request.getString("id");
+			} else {
+				messageId = UUID.randomUUID().toString();
+				request.put("id", messageId);
+			}
+
+			sendMessage(request.toString());
+		} catch (final XMPPException e) {
+			log.error("Unable to send JSON-RPC request: " + request.toString(), e);
+			throw new ClientError(e);
+		} catch (final JSONException e) {
+			log.error("Unable to get/set id on JSON-RPC request", e);
+			throw new ClientError(e);
+		}
+
+		// wait for the result
+		final String receiveString = nextMessage(timeout);
+		if (receiveString != null) {
+			log.debug("receive: " + receiveString);
+
+			try {
+				final JSONObject response = new JSONObject(receiveString);
+				if (response.has("id") && messageId.equals(response.getString("id"))) {
+					return response;
+				}
+			} catch (final JSONException e) {
+				// okay
+				log.debug("Ignoring non-JSON message");
+			}
+		}
+
+		throw new ClientError("Timed out after " + timeout + "ms while waiting for a response");
+	}
+
+	@Override
+	public void close() {
+		xmppConnection.disconnect();
+	}
+
+	/**
+	 * Wait for and retrieve the next message in this session.
+	 * @param timeout miliseconds to wait for the next message
+	 * @return the message, or <code>null<code> when this method timed out
+	 */
+	protected abstract String nextMessage(long timeout);
+
+	/**
+	 * Send the message
+	 * @param message
+	 * @throws XMPPException
+	 */
+	protected abstract void sendMessage(String message) throws XMPPException;
+
+	/**
+	 * @return the xmppConnection
+	 */
+	public XMPPConnection getXmppConnection() {
+		return xmppConnection;
+	}
+
+	/**
+	 * @return the timeout
+	 */
+	public long getTimeout() {
+		return timeout;
+	}
+
+	/**
+	 * @param timeout the timeout to set
+	 */
+	public void setTimeout(final long timeout) {
+		this.timeout = timeout;
+	}
+
+}
diff --git a/src/org/jabsorb/client/XMPPSessionFactory.java b/src/org/jabsorb/client/XMPPSessionFactory.java
new file mode 100644
index 0000000..1cfd505
--- /dev/null
+++ b/src/org/jabsorb/client/XMPPSessionFactory.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package org.jabsorb.client;
+
+import java.net.URI;
+
+import org.jabsorb.client.TransportRegistry.SessionFactory;
+import org.jabsorb.util.XMPPURI;
+import org.jivesoftware.smack.XMPPException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author matthijs
+ *
+ */
+public class XMPPSessionFactory implements SessionFactory {
+	private static final Logger log = LoggerFactory.getLogger(XMPPSessionFactory.class);
+
+	@Override
+	public Session newSession(final URI uri) {
+		final XMPPURI xmppuri = new XMPPURI(uri);
+
+		if (xmppuri.getRecipient() != null && !"".equals(xmppuri.getRecipient())) {
+			try {
+				return new XMPPChatSession(xmppuri);
+			} catch (final XMPPException e) {
+				log.error("Exception while creating XMPPChatSession", e);
+			}
+		} else if (xmppuri.getRoom() != null && !"".equals(xmppuri.getRoom())) {
+			try {
+				return new XMPPMultiUserChatSession(xmppuri);
+			} catch (final XMPPException e) {
+				log.error("Exception while creating XMPPMultiUserChatSession", e);
+			}
+		}
+
+		log.error("No recipient or chatroom specified. Can't create Session");
+		return null;
+	}
+
+	/**
+	 * Register this transport in 'registry'
+	 */
+	public static void register(final TransportRegistry registry) {
+		registry.registerTransport("xmpp", new XMPPSessionFactory());
+	}
+}
diff --git a/src/org/jabsorb/serializer/impl/PointSerializer.java b/src/org/jabsorb/serializer/impl/PointSerializer.java
new file mode 100644
index 0000000..8862018
--- /dev/null
+++ b/src/org/jabsorb/serializer/impl/PointSerializer.java
@@ -0,0 +1,93 @@
+/**
+ *
+ */
+package org.jabsorb.serializer.impl;
+
+import java.awt.Point;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jabsorb.JSONSerializer;
+import org.jabsorb.serializer.AbstractSerializer;
+import org.jabsorb.serializer.MarshallException;
+import org.jabsorb.serializer.ObjectMatch;
+import org.jabsorb.serializer.SerializerState;
+import org.jabsorb.serializer.UnmarshallException;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Serializes and Deserializes java.awt.Point values.
+ *
+ * @author matthijs
+ *
+ */
+public class PointSerializer extends AbstractSerializer {
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public Class<?>[] getSerializableClasses() {
+		return new Class<?>[] { Point.class };
+	}
+
+	@Override
+	public Class<?>[] getJSONClasses() {
+		return new Class[] { JSONObject.class };
+	}
+
+	@Override
+	public Object marshall(final SerializerState state, final Object p, final Object o) throws MarshallException {
+		if (!(o instanceof Point)) {
+			throw new MarshallException("Object to marshall is not a java.awt.Point!");
+		}
+
+		final Point point = (Point) o;
+		final Map<String, Integer> pointMap = new HashMap<String, Integer>();
+		pointMap.put("x", point.x);
+		pointMap.put("y", point.y);
+
+		return marshallHints(new JSONObject(pointMap), point);
+	}
+
+	@Override
+	public ObjectMatch tryUnmarshall(final SerializerState state, final Class<?> clazz, final Object json) throws UnmarshallException {
+	    final JSONObject jsonObject = (JSONObject) json;
+	    final ObjectMatch match;
+	    try {
+			if ((jsonObject.has(JSONSerializer.JAVA_CLASS_FIELD) &&
+				jsonObject.getString(JSONSerializer.JAVA_CLASS_FIELD).equals(Point.class.getName())) ||
+				(jsonObject.has("x") && jsonObject.has("y") && jsonObject.length() == 2)) {
+				match = ObjectMatch.OKAY;
+			} else {
+				if (jsonObject.has("x") || jsonObject.has("y")) {
+					match = ObjectMatch.SIMILAR;
+				} else {
+					match = ObjectMatch.ROUGHLY_SIMILAR;
+				}
+			}
+		} catch (final JSONException e) {
+			throw new UnmarshallException("Could not get javaClass field, even though jsonObject says it has one (should not happen)", e);
+		}
+
+	    state.setSerialized(json, match);
+	    return match;
+	}
+
+	@Override
+	public Object unmarshall(final SerializerState state, final Class<?> clazz, final Object json) throws UnmarshallException {
+	    final JSONObject jsonObject = (JSONObject) json;
+	    final Point point = new Point();
+	    try {
+		    if (jsonObject.has("x")) {
+		    	point.x = jsonObject.getInt("x");
+		    }
+		    if (jsonObject.has("y")) {
+		    	point.y = jsonObject.getInt("y");
+		    }
+	    } catch (final JSONException e) {
+	    	throw new UnmarshallException("Could not read field", e);
+	    }
+
+	    return point;
+	}
+}
diff --git a/src/org/jabsorb/util/XMPPConnectionManager.java b/src/org/jabsorb/util/XMPPConnectionManager.java
new file mode 100644
index 0000000..9bcebae
--- /dev/null
+++ b/src/org/jabsorb/util/XMPPConnectionManager.java
@@ -0,0 +1,87 @@
+/**
+ *
+ */
+package org.jabsorb.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jivesoftware.smack.ConnectionConfiguration;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Presence;
+
+/**
+ * Singleton to manage XMPPConnections
+ * @author matthijs
+ *
+ */
+public class XMPPConnectionManager {
+
+	private static XMPPConnectionManager instance;
+
+	private final Map<XMPPURI, XMPPConnection> managedConnections = new HashMap<XMPPURI, XMPPConnection>();
+	private final Map<XMPPConnection, Integer> leases = new HashMap<XMPPConnection, Integer>();
+
+	/**
+	 * Not to be instantiated outside this class
+	 */
+	private XMPPConnectionManager() {}
+
+	public XMPPConnection getXMPPConnection(final String uri) throws XMPPException {
+		return getXMPPConnection(new XMPPURI(uri));
+	}
+
+	public XMPPConnection getXMPPConnection(final XMPPURI uri) throws XMPPException {
+		final XMPPURI connectionUri = new XMPPURI();
+		connectionUri.setHost(uri.getHost());
+		connectionUri.setPort(uri.getPort());
+		connectionUri.setUser(uri.getUser());
+		connectionUri.setUserPassword(uri.getUserPassword());
+
+		final XMPPConnection connection;
+		if (managedConnections.containsKey(connectionUri)) {
+			connection = managedConnections.get(connectionUri);
+		} else {
+			connection = new XMPPConnection(new ConnectionConfiguration(connectionUri.getHost(), connectionUri.getPort())) {
+				@Override
+				protected void shutdown(final Presence unavailablePresence) {
+					// decrement lease or shutdown
+					final int lease = leases.get(this);
+					if (lease > 1) {
+						leases.put(this, lease - 1);
+					} else {
+						leases.remove(this);
+						super.shutdown(unavailablePresence);
+					}
+				}
+			};
+		}
+
+		// increment lease
+		final int lease = leases.containsKey(connection.getConnectionID()) ? leases.get(connection.getConnectionID()) + 1 : 1;
+		leases.put(connection, lease);
+
+		if (!connection.isConnected()) {
+			connection.connect();
+		}
+
+		if (!connection.isAuthenticated()) {
+			if (connectionUri.getUser() != null) {
+				connection.login(connectionUri.getUser(), connectionUri.getUserPassword(), "JSONRPC");
+			} else {
+				connection.loginAnonymously();
+			}
+		}
+
+		return connection;
+	}
+
+	public static XMPPConnectionManager getInstance() {
+		if (instance == null) {
+			instance = new XMPPConnectionManager();
+		}
+
+		return instance;
+	}
+}
diff --git a/src/org/jabsorb/util/XMPPURI.java b/src/org/jabsorb/util/XMPPURI.java
new file mode 100644
index 0000000..58539de
--- /dev/null
+++ b/src/org/jabsorb/util/XMPPURI.java
@@ -0,0 +1,275 @@
+package org.jabsorb.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class to simplify the creation of RFC5122 URI's
+ * @author matthijs
+ *
+ */
+public class XMPPURI {
+	private String host;
+	private int port;
+	private String user;
+	private String userPassword;
+	private String recipient;
+	private String room;
+	private String roomPassword;
+
+	public XMPPURI(final String uri) {
+		this(URI.create(uri));
+	}
+
+	public XMPPURI(final URI uri) {
+		this.host = uri.getHost();
+		this.port = uri.getPort();
+
+		final String userInfo = uri.getUserInfo();
+		if (userInfo != null) {
+			final String[] userPass = userInfo.split(":", 2);
+			this.user = userPass[0];
+			if (userPass.length == 2) {
+				this.userPassword = userPass[1];
+			}
+		}
+
+		final String path = uri.getPath().replaceFirst("/", "");
+		final String query = uri.getQuery();
+		if (query != null) {
+			final List<String> parameters = Arrays.asList(query.split(";"));
+			if (parameters.contains("join")) {
+				// http://xmpp.org/extensions/xep-0045.html#registrar-querytypes
+				this.room = path;
+
+				for (final String param : parameters) {
+					if (param.startsWith("password=")) {
+						this.roomPassword = param.replaceFirst("password=", "");
+					}
+				}
+			}
+		}
+
+		if (this.room == null) {
+			if (path != null) {
+				this.recipient = path;
+			}
+		}
+	}
+
+	public XMPPURI() {
+	}
+
+	public URI toURI() throws URISyntaxException {
+		final StringBuilder userInfo = new StringBuilder();
+		if (user != null) {
+			userInfo.append(user);
+			if (userPassword != null) {
+				userInfo.append(":").append(userPassword);
+			}
+		}
+
+		String path;
+		final StringBuilder queryBuilder = new StringBuilder();
+		if (recipient != null) {
+			path = recipient;
+		} else if (room != null) {
+			path = room;
+			queryBuilder.append("join");
+			if (roomPassword != null) {
+				queryBuilder.append(";password=").append(roomPassword);
+			}
+		} else {
+			path = null;
+		}
+
+		final String query = queryBuilder.length() > 0 ? queryBuilder.toString() : null;
+		return new URI("xmpp", userInfo.toString(), host, port, path, query, null);
+	}
+
+	/**
+	 * @return the host
+	 */
+	public String getHost() {
+		return host;
+	}
+
+	/**
+	 * @param host the host to set
+	 */
+	public void setHost(final String host) {
+		this.host = host;
+	}
+
+	/**
+	 * @return the port
+	 */
+	public int getPort() {
+		return port;
+	}
+
+	/**
+	 * @param port the port to set
+	 */
+	public void setPort(final int port) {
+		this.port = port;
+	}
+
+	/**
+	 * @return the user
+	 */
+	public String getUser() {
+		return user;
+	}
+
+	/**
+	 * @param user the user to set
+	 */
+	public void setUser(final String user) {
+		this.user = user;
+	}
+
+	/**
+	 * @return the userPassword
+	 */
+	public String getUserPassword() {
+		return userPassword;
+	}
+
+	/**
+	 * @param userPassword the userPassword to set
+	 */
+	public void setUserPassword(final String userPassword) {
+		this.userPassword = userPassword;
+	}
+
+	/**
+	 * @return the recipient
+	 */
+	public String getRecipient() {
+		return recipient;
+	}
+
+	/**
+	 * @param recipient the recipient to set
+	 */
+	public void setRecipient(final String recipient) {
+		this.recipient = recipient;
+	}
+
+	/**
+	 * @return the room
+	 */
+	public String getRoom() {
+		return room;
+	}
+
+	/**
+	 * @param room the room to set
+	 */
+	public void setRoom(final String room) {
+		this.room = room;
+	}
+
+	/**
+	 * @return the roomPassword
+	 */
+	public String getRoomPassword() {
+		return roomPassword;
+	}
+
+	/**
+	 * @param roomPassword the roomPassword to set
+	 */
+	public void setRoomPassword(final String roomPassword) {
+		this.roomPassword = roomPassword;
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (!(obj instanceof XMPPURI)) {
+			return false;
+		}
+		final XMPPURI other = (XMPPURI) obj;
+		if (host == null) {
+			if (other.host != null) {
+				return false;
+			}
+		} else if (!host.equals(other.host)) {
+			return false;
+		}
+		if (port != other.port) {
+			return false;
+		}
+		if (recipient == null) {
+			if (other.recipient != null) {
+				return false;
+			}
+		} else if (!recipient.equals(other.recipient)) {
+			return false;
+		}
+		if (room == null) {
+			if (other.room != null) {
+				return false;
+			}
+		} else if (!room.equals(other.room)) {
+			return false;
+		}
+		if (roomPassword == null) {
+			if (other.roomPassword != null) {
+				return false;
+			}
+		} else if (!roomPassword.equals(other.roomPassword)) {
+			return false;
+		}
+		if (user == null) {
+			if (other.user != null) {
+				return false;
+			}
+		} else if (!user.equals(other.user)) {
+			return false;
+		}
+		if (userPassword == null) {
+			if (other.userPassword != null) {
+				return false;
+			}
+		} else if (!userPassword.equals(other.userPassword)) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((host == null) ? 0 : host.hashCode());
+		result = prime * result + port;
+		result = prime * result
+				+ ((recipient == null) ? 0 : recipient.hashCode());
+		result = prime * result + ((room == null) ? 0 : room.hashCode());
+		result = prime * result
+				+ ((roomPassword == null) ? 0 : roomPassword.hashCode());
+		result = prime * result + ((user == null) ? 0 : user.hashCode());
+		result = prime * result
+				+ ((userPassword == null) ? 0 : userPassword.hashCode());
+		return result;
+	}
+
+	@Override
+	public String toString() {
+		try {
+			return toURI().toString();
+		} catch (final URISyntaxException e) {
+			return super.toString();
+		}
+	}
+}
