2004-08-16 02:57:36 +02:00
|
|
|
/**
|
2004-08-17 09:44:36 +02:00
|
|
|
* $RCSfile$
|
|
|
|
* $Revision$
|
|
|
|
* $Date$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2002-2004 Jive Software. All rights reserved.
|
|
|
|
* ====================================================================
|
|
|
|
* The Jive Software License (based on Apache Software License, Version 1.1)
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in
|
|
|
|
* the documentation and/or other materials provided with the
|
|
|
|
* distribution.
|
|
|
|
*
|
|
|
|
* 3. The end-user documentation included with the redistribution,
|
|
|
|
* if any, must include the following acknowledgment:
|
|
|
|
* "This product includes software developed by
|
|
|
|
* Jive Software (http://www.jivesoftware.com)."
|
|
|
|
* Alternately, this acknowledgment may appear in the software itself,
|
|
|
|
* if and wherever such third-party acknowledgments normally appear.
|
|
|
|
*
|
|
|
|
* 4. The names "Smack" and "Jive Software" must not be used to
|
|
|
|
* endorse or promote products derived from this software without
|
|
|
|
* prior written permission. For written permission, please
|
|
|
|
* contact webmaster@jivesoftware.com.
|
|
|
|
*
|
|
|
|
* 5. Products derived from this software may not be called "Smack",
|
|
|
|
* nor may "Smack" appear in their name, without prior written
|
|
|
|
* permission of Jive Software.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
|
|
|
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
* ====================================================================
|
|
|
|
*/
|
2004-08-16 02:57:36 +02:00
|
|
|
|
|
|
|
package org.jivesoftware.smackx.workgroup.user;
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
import org.jivesoftware.smack.*;
|
|
|
|
import org.jivesoftware.smack.filter.*;
|
|
|
|
import org.jivesoftware.smack.packet.*;
|
|
|
|
import org.jivesoftware.smackx.GroupChatInvitation;
|
|
|
|
|
|
|
|
import org.jivesoftware.smackx.workgroup.packet.*;
|
|
|
|
import org.jivesoftware.smackx.workgroup.util.MetaDataUtils;
|
|
|
|
|
|
|
|
/**
|
2004-08-17 09:44:36 +02:00
|
|
|
* Provides workgroup services for users. Users can join the workgroup queue, depart the
|
2004-08-16 02:57:36 +02:00
|
|
|
* queue, find status information about their placement in the queue, and register to
|
|
|
|
* be notified when they are routed to an agent.<p>
|
|
|
|
*
|
|
|
|
* This class only provides a user's perspective into a workgroup and is not intended
|
|
|
|
* for use by agents.
|
|
|
|
*
|
|
|
|
* @author Matt Tucker
|
|
|
|
*/
|
|
|
|
public class Workgroup {
|
|
|
|
|
|
|
|
private String workgroupName;
|
|
|
|
private XMPPConnection connection;
|
|
|
|
private boolean inQueue;
|
|
|
|
private List queueListeners;
|
|
|
|
|
|
|
|
private int queuePosition = -1;
|
|
|
|
private int queueRemainingTime = -1;
|
|
|
|
|
2004-08-17 09:44:36 +02:00
|
|
|
private PacketListener packetListener;
|
|
|
|
|
2004-08-16 02:57:36 +02:00
|
|
|
/**
|
|
|
|
* Creates a new workgroup instance using the specified workgroup name
|
|
|
|
* (eg support@example.com) and XMPP connection. The connection must have
|
|
|
|
* undergone a successful login before being used to construct an instance of
|
|
|
|
* this class.
|
|
|
|
*
|
|
|
|
* @param workgroupName the fully qualified name of the workgroup.
|
|
|
|
* @param connection an XMPP connection which must have already undergone a
|
|
|
|
* successful login.
|
|
|
|
*/
|
|
|
|
public Workgroup(String workgroupName, XMPPConnection connection) {
|
|
|
|
// Login must have been done before passing in connection.
|
|
|
|
if (!connection.isAuthenticated()) {
|
|
|
|
throw new IllegalStateException("Must login to server before creating workgroup.");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.workgroupName = workgroupName;
|
|
|
|
this.connection = connection;
|
|
|
|
inQueue = false;
|
|
|
|
queueListeners = new ArrayList();
|
|
|
|
|
|
|
|
// Register as a queue listener for internal usage by this instance.
|
|
|
|
addQueueListener(new QueueListener() {
|
|
|
|
public void joinedQueue() {
|
|
|
|
inQueue = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void departedQueue() {
|
|
|
|
inQueue = false;
|
|
|
|
queuePosition = -1;
|
|
|
|
queueRemainingTime = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void queuePositionUpdated(int currentPosition) {
|
|
|
|
queuePosition = currentPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void queueWaitTimeUpdated(int secondsRemaining) {
|
|
|
|
queueRemainingTime = secondsRemaining;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register an invitation listener for internal usage by this instance.
|
2004-08-17 09:44:36 +02:00
|
|
|
addInvitationListener(new PacketListener() {
|
|
|
|
|
|
|
|
public void processPacket(Packet packet) {
|
|
|
|
GroupChatInvitation invitation = (GroupChatInvitation)packet.getExtension(
|
|
|
|
GroupChatInvitation.ELEMENT_NAME, GroupChatInvitation.NAMESPACE);
|
|
|
|
if (invitation != null) {
|
|
|
|
inQueue = false;
|
|
|
|
queuePosition = -1;
|
|
|
|
queueRemainingTime = -1;
|
|
|
|
}
|
2004-08-16 02:57:36 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register a packet listener for all queue events.
|
|
|
|
PacketFilter orFilter = new OrFilter(new PacketTypeFilter(Message.class),
|
|
|
|
new PacketTypeFilter(QueueUpdate.class));
|
|
|
|
|
|
|
|
PacketFilter filter = new AndFilter(new FromContainsFilter(this.workgroupName), orFilter);
|
|
|
|
|
2004-08-17 09:44:36 +02:00
|
|
|
packetListener = new PacketListener() {
|
|
|
|
|
|
|
|
public void processPacket(Packet packet) {
|
|
|
|
handlePacket(packet);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
connection.addPacketListener(packetListener, filter);
|
2004-08-16 02:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the name of this workgroup (eg support@example.com).
|
|
|
|
*
|
|
|
|
* @return the name of the workgroup.
|
|
|
|
*/
|
|
|
|
public String getWorkgroupName() {
|
|
|
|
return workgroupName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the user is currently waiting in the workgroup queue.
|
|
|
|
*
|
|
|
|
* @return true if currently waiting in the queue.
|
|
|
|
*/
|
|
|
|
public boolean isInQueue() {
|
|
|
|
return inQueue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the user's current position in the workgroup queue. A value of 0 means
|
|
|
|
* the user is next in line to be routed; therefore, if the queue position
|
|
|
|
* is being displayed to the end user it is usually a good idea to add 1 to
|
|
|
|
* the value this method returns before display. If the user is not currently
|
|
|
|
* waiting in the workgorup, or no queue position information is available, -1
|
|
|
|
* will be returned.
|
|
|
|
*
|
|
|
|
* @return the user's current position in the workgorup queue, or -1 if the
|
|
|
|
* position isn't available or if the user isn't in the queue.
|
|
|
|
*/
|
|
|
|
public int getQueuePosition() {
|
|
|
|
return queuePosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the estimated time (in seconds) that the user has to left wait in
|
|
|
|
* the workgroup queue before being routed. If the user is not currently waiting
|
|
|
|
* int he workgroup, or no queue time information is available, -1 will be
|
|
|
|
* returned.
|
|
|
|
*
|
|
|
|
* @return the estimated time remaining (in seconds) that the user has to
|
|
|
|
* wait in the workgropu queue, or -1 if time information isn't available
|
|
|
|
* or if the user isn't int the queue.
|
|
|
|
*/
|
|
|
|
public int getQueueRemainingTime() {
|
|
|
|
return queueRemainingTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins the workgroup queue to wait to be routed to an agent. After joining
|
|
|
|
* the queue, queue status events will be sent to indicate the user's position and
|
|
|
|
* estimated time left in the queue. Once joining the queue, there are three ways
|
|
|
|
* the user will leave the queue: <ul>
|
|
|
|
* <li>The user is routed to an agent, which triggers a groupchat invitation.
|
|
|
|
* <li>The user asks to leave the queue by calling the {@link #departQueue} method.
|
|
|
|
* <li>A server error occurs, or an administrator explicitly removes the user
|
|
|
|
* from the queue.
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* A user cannot request to join the queue again if already in the queue. Therefore, this
|
|
|
|
* method will do nothing if the user is already in the queue.<p>
|
|
|
|
*
|
|
|
|
* Some servers may be configured to require certain meta-data in
|
|
|
|
* order to join the queue. In that case, the {@link #joinQueue(Map)} method
|
|
|
|
* should be used instead of this method so that meta-data may be passed in.
|
|
|
|
*
|
|
|
|
* @throws XMPPException if an error occured joining the queue. An error may indicate
|
|
|
|
* that a connection failure occured or that the server explicitly rejected the
|
|
|
|
* request to join the queue.
|
|
|
|
*/
|
|
|
|
public void joinQueue() throws XMPPException {
|
|
|
|
joinQueue(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins the workgroup queue to wait to be routed to an agent. After joining
|
|
|
|
* the queue, queue status events will be sent to indicate the user's position and
|
|
|
|
* estimated time left in the queue. Once joining the queue, there are three ways
|
|
|
|
* the user will leave the queue: <ul>
|
|
|
|
* <li>The user is routed to an agent, which triggers a groupchat invitation.
|
|
|
|
* <li>The user asks to leave the queue by calling the {@link #departQueue} method.
|
|
|
|
* <li>A server error occurs, or an administrator explicitly removes the user
|
|
|
|
* from the queue.
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* A user cannot request to join the queue again if already in the queue. Therefore, this
|
|
|
|
* method will do nothing if the user is already in the queue.<p>
|
|
|
|
*
|
|
|
|
* Arbitrary meta-data can be passed in with the queue join request in order to assist
|
|
|
|
* the server in routing the user to an agent and to provide information about the
|
|
|
|
* user to the agent. Some servers may be configured to require certain meta-data in
|
|
|
|
* order to join the queue.<p>
|
|
|
|
*
|
|
|
|
* The server may reject the join queue request, which will cause an XMPPException to
|
|
|
|
* be thrown. The error codes for specific cases are as follows:<ul>
|
|
|
|
*
|
|
|
|
* <li>503 -- the workgroup is closed or otherwise unavailable to take
|
|
|
|
* new chat requests.
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* @param metaData the metaData for the join request.
|
|
|
|
* @throws XMPPException if an error occured joining the queue. An error may indicate
|
|
|
|
* that a connection failure occured or that the server explicitly rejected the
|
|
|
|
* request to join the queue (error code 503). The error code should be checked
|
|
|
|
* to determine the specific error.
|
|
|
|
*/
|
|
|
|
public void joinQueue(Map metaData) throws XMPPException {
|
|
|
|
// If already in the queue ignore the join request.
|
|
|
|
if (inQueue) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupName, metaData);
|
|
|
|
PacketCollector collector = connection.createPacketCollector(
|
|
|
|
new PacketIDFilter(joinPacket.getPacketID()));
|
|
|
|
|
|
|
|
this.connection.sendPacket(joinPacket);
|
|
|
|
|
|
|
|
IQ response = (IQ)collector.nextResult(10000);
|
|
|
|
|
|
|
|
// Cancel the collector.
|
|
|
|
collector.cancel();
|
|
|
|
if (response == null) {
|
|
|
|
throw new XMPPException("No response from the server.");
|
|
|
|
}
|
|
|
|
if (response.getError() != null) {
|
|
|
|
throw new XMPPException(response.getError());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify listeners that we've joined the queue.
|
|
|
|
fireQueueJoinedEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Departs the workgroup queue. If the user is not currently in the queue, this
|
|
|
|
* method will do nothing.<p>
|
|
|
|
*
|
|
|
|
* Normally, the user would not manually leave the queue. However, they may wish to
|
|
|
|
* under certain circumstances -- for example, if they no longer wish to be routed
|
|
|
|
* to an agent because they've been waiting too long.
|
|
|
|
*
|
|
|
|
* @throws XMPPException if an error occured trying to send the depart queue
|
|
|
|
* request to the server.
|
|
|
|
*/
|
|
|
|
public void departQueue() throws XMPPException {
|
|
|
|
// If not in the queue ignore the depart request.
|
|
|
|
if (!inQueue) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupName);
|
|
|
|
PacketCollector collector = this.connection.createPacketCollector(
|
|
|
|
new PacketIDFilter(departPacket.getPacketID()));
|
|
|
|
|
|
|
|
connection.sendPacket(departPacket);
|
|
|
|
|
|
|
|
IQ response = (IQ)collector.nextResult(5000);
|
|
|
|
collector.cancel();
|
|
|
|
if (response == null) {
|
|
|
|
throw new XMPPException("No response from the server.");
|
|
|
|
}
|
|
|
|
if (response.getError() != null) {
|
|
|
|
throw new XMPPException(response.getError());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify listeners that we're no longer in the queue.
|
|
|
|
fireQueueDepartedEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a queue listener that will be notified of queue events for the user
|
|
|
|
* that created this Workgroup instance.
|
|
|
|
*
|
|
|
|
* @param queueListener the queue listener.
|
|
|
|
*/
|
|
|
|
public void addQueueListener(QueueListener queueListener) {
|
|
|
|
synchronized(queueListeners) {
|
|
|
|
if (!queueListeners.contains(queueListener)) {
|
|
|
|
queueListeners.add(queueListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a queue listener.
|
|
|
|
*
|
|
|
|
* @param queueListener the queue listener.
|
|
|
|
*/
|
|
|
|
public void removeQueueListener(QueueListener queueListener) {
|
|
|
|
synchronized(queueListeners) {
|
|
|
|
queueListeners.remove(queueListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds an invitation listener that will be notified of groupchat invitations
|
|
|
|
* from the workgroup for the the user that created this Workgroup instance.
|
|
|
|
*
|
2004-08-17 09:44:36 +02:00
|
|
|
* @param packetListener the invitation listener.
|
2004-08-16 02:57:36 +02:00
|
|
|
*/
|
2004-08-17 09:44:36 +02:00
|
|
|
public void addInvitationListener(PacketListener packetListener) {
|
|
|
|
connection.addPacketListener(packetListener, null);
|
2004-08-16 02:57:36 +02:00
|
|
|
}
|
|
|
|
|
2004-08-17 09:44:36 +02:00
|
|
|
protected void finalize() throws Throwable {
|
|
|
|
connection.removePacketListener(packetListener);
|
2004-08-16 02:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void fireQueueJoinedEvent() {
|
|
|
|
synchronized (queueListeners) {
|
|
|
|
for (Iterator i=queueListeners.iterator(); i.hasNext(); ) {
|
|
|
|
QueueListener listener = (QueueListener)i.next();
|
|
|
|
listener.joinedQueue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fireQueueDepartedEvent() {
|
|
|
|
synchronized (queueListeners) {
|
|
|
|
for (Iterator i=queueListeners.iterator(); i.hasNext(); ) {
|
|
|
|
QueueListener listener = (QueueListener)i.next();
|
|
|
|
listener.departedQueue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fireQueuePositionEvent(int currentPosition) {
|
|
|
|
synchronized (queueListeners) {
|
|
|
|
for (Iterator i=queueListeners.iterator(); i.hasNext(); ) {
|
|
|
|
QueueListener listener = (QueueListener)i.next();
|
|
|
|
listener.queuePositionUpdated(currentPosition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fireQueueTimeEvent(int secondsRemaining) {
|
|
|
|
synchronized (queueListeners) {
|
|
|
|
for (Iterator i=queueListeners.iterator(); i.hasNext(); ) {
|
|
|
|
QueueListener listener = (QueueListener)i.next();
|
|
|
|
listener.queueWaitTimeUpdated(secondsRemaining);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PacketListener Implementation.
|
|
|
|
|
|
|
|
private void handlePacket(Packet packet) {
|
|
|
|
if (packet instanceof Message) {
|
|
|
|
Message msg = (Message)packet;
|
|
|
|
// Check to see if the user left the queue.
|
|
|
|
PacketExtension pe = msg.getExtension("depart-queue", "xmpp:workgroup");
|
|
|
|
|
|
|
|
if (pe != null) {
|
|
|
|
fireQueueDepartedEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Check to see if it's a queue update notification.
|
|
|
|
else if (packet instanceof QueueUpdate) {
|
|
|
|
QueueUpdate queueUpdate = (QueueUpdate)packet;
|
|
|
|
if (queueUpdate.getPosition() != -1) {
|
|
|
|
fireQueuePositionEvent(queueUpdate.getPosition());
|
|
|
|
}
|
|
|
|
if (queueUpdate.getRemaingTime() != -1) {
|
|
|
|
fireQueueTimeEvent(queueUpdate.getRemaingTime());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* IQ packet to request joining the workgroup queue.
|
|
|
|
*/
|
|
|
|
private class JoinQueuePacket extends IQ {
|
|
|
|
|
|
|
|
private Map metaData;
|
|
|
|
|
|
|
|
public JoinQueuePacket(String workgroup, Map metaData) {
|
|
|
|
this.metaData = metaData;
|
|
|
|
|
|
|
|
setTo(workgroup);
|
|
|
|
setType(IQ.Type.SET);
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getChildElementXML() {
|
|
|
|
StringBuffer buf = new StringBuffer();
|
|
|
|
|
|
|
|
buf.append("<join-queue xmlns=\"xmpp:workgroup\">");
|
|
|
|
buf.append("<queue-notifications/>");
|
|
|
|
|
|
|
|
// Add any meta-data.
|
2004-08-17 09:44:36 +02:00
|
|
|
buf.append(MetaDataUtils.encodeMetaData(metaData));
|
2004-08-16 02:57:36 +02:00
|
|
|
|
|
|
|
buf.append("</join-queue>");
|
|
|
|
|
|
|
|
return buf.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|