From 14b50d790a511f5beaaec63dab4bdacf0cdb7ea6 Mon Sep 17 00:00:00 2001 From: Gaston Dombiak Date: Tue, 18 Jul 2006 05:04:47 +0000 Subject: [PATCH] Initial version. SMACK-155 git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@4537 b35dd754-fafc-0310-a699-88a17e54d16e --- .../ConnectionDetachedPacketCollector.java | 135 +++++++++++ .../smackx/muc/PacketMultiplexListener.java | 96 ++++++++ .../smackx/muc/RoomListenerMultiplexor.java | 215 ++++++++++++++++++ 3 files changed, 446 insertions(+) create mode 100644 source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java create mode 100644 source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java create mode 100644 source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java diff --git a/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java b/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java new file mode 100644 index 000000000..600149b0b --- /dev/null +++ b/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java @@ -0,0 +1,135 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.packet.Packet; + +import java.util.LinkedList; + +/** + * A variant of the {@link org.jivesoftware.smack.PacketCollector} class + * that does not force attachment to an XMPPConnection + * on creation and no filter is required. Used to collect message + * packets targeted to a group chat room. + * + * @author Larry Kirschner + */ +class ConnectionDetachedPacketCollector { + /** + * Max number of packets that any one collector can hold. After the max is + * reached, older packets will be automatically dropped from the queue as + * new packets are added. + */ + private static final int MAX_PACKETS = 65536; + + private LinkedList resultQueue; + + /** + * Creates a new packet collector. If the packet filter is null, then + * all packets will match this collector. + */ + public ConnectionDetachedPacketCollector() { + this.resultQueue = new LinkedList(); + } + + /** + * Polls to see if a packet is currently available and returns it, or + * immediately returns null if no packets are currently in the + * result queue. + * + * @return the next packet result, or null if there are no more + * results. + */ + public synchronized Packet pollResult() { + if (resultQueue.isEmpty()) { + return null; + } + else { + return resultQueue.removeLast(); + } + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available. + * + * @return the next available packet. + */ + public synchronized Packet nextResult() { + // Wait indefinitely until there is a result to return. + while (resultQueue.isEmpty()) { + try { + wait(); + } + catch (InterruptedException ie) { + // Ignore. + } + } + return resultQueue.removeLast(); + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available or the timeout has elapased. If the + * timeout elapses without a result, null will be returned. + * + * @param timeout the amount of time to wait for the next packet (in milleseconds). + * @return the next available packet. + */ + public synchronized Packet nextResult(long timeout) { + // Wait up to the specified amount of time for a result. + if (resultQueue.isEmpty()) { + try { + wait(timeout); + } + catch (InterruptedException ie) { + // Ignore. + } + } + // If still no result, return null. + if (resultQueue.isEmpty()) { + return null; + } + else { + return resultQueue.removeLast(); + } + } + + /** + * Processes a packet to see if it meets the criteria for this packet collector. + * If so, the packet is added to the result queue. + * + * @param packet the packet to process. + */ + protected synchronized void processPacket(Packet packet) { + if (packet == null) { + return; + } + // If the max number of packets has been reached, remove the oldest one. + if (resultQueue.size() == MAX_PACKETS) { + resultQueue.removeLast(); + } + // Add the new packet. + resultQueue.addFirst(packet); + // Notify waiting threads a result is available. + notifyAll(); + } +} diff --git a/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java b/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java new file mode 100644 index 000000000..2ff7aa939 --- /dev/null +++ b/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java @@ -0,0 +1,96 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; + +/** + * The single PacketListener used by each {@link MultiplexedMultiUserChat} + * for all basic processing of presence, and message packets targeted to that chat. + * + * @author Larry Kirschner + */ +class PacketMultiplexListener implements PacketListener { + + private static final PacketFilter MESSAGE_FILTER = + new MessageTypeFilter(Message.Type.GROUP_CHAT); + private static final PacketFilter PRESENCE_FILTER = new PacketTypeFilter(Presence.class); + private static final PacketFilter SUBJECT_FILTER = new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getSubject() != null; + } + }; + private static final PacketFilter DECLINES_FILTER = + new PacketExtensionFilter("x", + "http://jabber.org/protocol/muc#user"); + + private ConnectionDetachedPacketCollector messageCollector; + private PacketListener presenceListener; + private PacketListener subjectListener; + private PacketListener declinesListener; + + public PacketMultiplexListener( + ConnectionDetachedPacketCollector messageCollector, + PacketListener presenceListener, + PacketListener subjectListener, PacketListener declinesListener) { + if (messageCollector == null) { + throw new IllegalArgumentException("MessageCollector is null"); + } + if (presenceListener == null) { + throw new IllegalArgumentException("Presence listener is null"); + } + if (subjectListener == null) { + throw new IllegalArgumentException("Subject listener is null"); + } + if (declinesListener == null) { + throw new IllegalArgumentException("Declines listener is null"); + } + this.messageCollector = messageCollector; + this.presenceListener = presenceListener; + this.subjectListener = subjectListener; + this.declinesListener = declinesListener; + } + + public void processPacket(Packet p) { + if (PRESENCE_FILTER.accept(p)) { + presenceListener.processPacket(p); + } + else if (MESSAGE_FILTER.accept(p)) { + messageCollector.processPacket(p); + + if (SUBJECT_FILTER.accept(p)) { + subjectListener.processPacket(p); + } + } + else if (DECLINES_FILTER.accept(p)) { + declinesListener.processPacket(p); + } + } + +} diff --git a/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java b/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java new file mode 100644 index 000000000..e756f92f5 --- /dev/null +++ b/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java @@ -0,0 +1,215 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.StringUtils; + +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A RoomListenerMultiplexor multiplexes incoming packets on + * an XMPPConnection using a single listener/filter pair. + * A single RoomListenerMultiplexor is created for each + * {@link org.jivesoftware.smack.XMPPConnection} that has joined MUC rooms + * within its session. + * + * @author Larry Kirschner + */ +class RoomListenerMultiplexor implements ConnectionListener { + + // We use a WeakHashMap so that the GC can collect the monitor when the + // connection is no longer referenced by any object. + private static final Map> monitors = + new WeakHashMap>(); + + private XMPPConnection connection; + private RoomMultiplexFilter filter; + private RoomMultiplexListener listener; + + /** + * Returns a new or existing RoomListenerMultiplexor for a given connection. + * + * @param conn the connection to monitor for room invitations. + * @return a new or existing RoomListenerMultiplexor for a given connection. + */ + public static RoomListenerMultiplexor getRoomMultiplexor(XMPPConnection conn) { + synchronized (monitors) { + if (!monitors.containsKey(conn)) { + RoomListenerMultiplexor rm = new RoomListenerMultiplexor(conn, new RoomMultiplexFilter(), + new RoomMultiplexListener()); + + rm.init(); + + // We need to use a WeakReference because the monitor references the + // connection and this could prevent the GC from collecting the monitor + // when no other object references the monitor + monitors.put(conn, new WeakReference(rm)); + } + // Return the InvitationsMonitor that monitors the connection + return monitors.get(conn).get(); + } + } + + /** + * All access should be through + * the static method {@link #getRoomMultiplexor(XMPPConnection)}. + */ + private RoomListenerMultiplexor(XMPPConnection connection, RoomMultiplexFilter filter, + RoomMultiplexListener listener) { + if (connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + if (filter == null) { + throw new IllegalArgumentException("Filter is null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener is null"); + } + this.connection = connection; + this.filter = filter; + this.listener = listener; + } + + public void addRoom(String address, PacketMultiplexListener roomListener) { + filter.addRoom(address); + listener.addRoom(address, roomListener); + } + + public void connectionClosed() { + cancel(); + } + + public void connectionClosedOnError(Exception e) { + cancel(); + } + + /** + * Initializes the listeners to detect received room invitations and to detect when the + * connection gets closed. As soon as a room invitation is received the invitations + * listeners will be fired. When the connection gets closed the monitor will remove + * his listeners on the connection. + */ + public void init() { + connection.addConnectionListener(this); + connection.addPacketListener(listener, filter); + } + + public void removeRoom(String address) { + filter.removeRoom(address); + listener.removeRoom(address); + } + + /** + * Cancels all the listeners that this InvitationsMonitor has added to the connection. + */ + private void cancel() { + connection.removeConnectionListener(this); + connection.removePacketListener(listener); + } + + /** + * The single XMPPConnection-level PacketFilter used by a {@link RoomListenerMultiplexor} + * for all muc chat rooms on an XMPPConnection. + * Each time a muc chat room is added to/removed from an + * XMPPConnection the address for that chat room + * is added to/removed from that XMPPConnection's + * RoomMultiplexFilter. + */ + private static class RoomMultiplexFilter implements PacketFilter { + + private Map roomAddressTable = new ConcurrentHashMap(); + + public boolean accept(Packet p) { + String from = p.getFrom(); + if (from == null) { + return false; + } + return roomAddressTable.containsKey(StringUtils.parseBareAddress(from).toLowerCase()); + } + + public void addRoom(String address) { + if (address == null) { + return; + } + roomAddressTable.put(address.toLowerCase(), address); + } + + public void removeRoom(String address) { + if (address == null) { + return; + } + roomAddressTable.remove(address.toLowerCase()); + } + } + + /** + * The single XMPPConnection-level PacketListener + * used by a {@link RoomListenerMultiplexor} + * for all muc chat rooms on an XMPPConnection. + * Each time a muc chat room is added to/removed from an + * XMPPConnection the address and listener for that chat room + * are added to/removed from that XMPPConnection's + * RoomMultiplexListener. + * + * @author Larry Kirschner + */ + private static class RoomMultiplexListener implements PacketListener { + + private Map roomListenersByAddress = + new ConcurrentHashMap(); + + public void processPacket(Packet p) { + String from = p.getFrom(); + if (from == null) { + return; + } + + PacketMultiplexListener listener = + roomListenersByAddress.get(StringUtils.parseBareAddress(from).toLowerCase()); + + if (listener != null) { + listener.processPacket(p); + } + } + + public void addRoom(String address, PacketMultiplexListener listener) { + if (address == null) { + return; + } + roomListenersByAddress.put(address.toLowerCase(), listener); + } + + public void removeRoom(String address) { + if (address == null) { + return; + } + roomListenersByAddress.remove(address.toLowerCase()); + } + } +}