aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/workgroup/agent
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/workgroup/agent')
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/Agent.java138
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java386
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java35
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/AgentSession.java1185
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java62
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/Offer.java223
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java114
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java32
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/OfferContent.java55
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/OfferListener.java49
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java60
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java98
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java100
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java111
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java62
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/UserRequest.java47
-rw-r--r--src/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java224
17 files changed, 2981 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/Agent.java b/src/org/jivesoftware/smackx/workgroup/agent/Agent.java
new file mode 100644
index 0000000..bebac37
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/Agent.java
@@ -0,0 +1,138 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smackx.workgroup.packet.AgentInfo;
+import org.jivesoftware.smackx.workgroup.packet.AgentWorkgroups;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+
+import java.util.Collection;
+
+/**
+ * The <code>Agent</code> class is used to represent one agent in a Workgroup Queue.
+ *
+ * @author Derek DeMoro
+ */
+public class Agent {
+ private Connection connection;
+ private String workgroupJID;
+
+ public static Collection<String> getWorkgroups(String serviceJID, String agentJID, Connection connection) throws XMPPException {
+ AgentWorkgroups request = new AgentWorkgroups(agentJID);
+ request.setTo(serviceJID);
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ // Send the request
+ connection.sendPacket(request);
+
+ AgentWorkgroups response = (AgentWorkgroups)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response.getWorkgroups();
+ }
+
+ /**
+ * Constructs an Agent.
+ */
+ Agent(Connection connection, String workgroupJID) {
+ this.connection = connection;
+ this.workgroupJID = workgroupJID;
+ }
+
+ /**
+ * Return the agents JID
+ *
+ * @return - the agents JID.
+ */
+ public String getUser() {
+ return connection.getUser();
+ }
+
+ /**
+ * Return the agents name.
+ *
+ * @return - the agents name.
+ */
+ public String getName() throws XMPPException {
+ AgentInfo agentInfo = new AgentInfo();
+ agentInfo.setType(IQ.Type.GET);
+ agentInfo.setTo(workgroupJID);
+ agentInfo.setFrom(getUser());
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID()));
+ // Send the request
+ connection.sendPacket(agentInfo);
+
+ AgentInfo response = (AgentInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response.getName();
+ }
+
+ /**
+ * Changes the name of the agent in the server. The server may have this functionality
+ * disabled for all the agents or for this agent in particular. If the agent is not
+ * allowed to change his name then an exception will be thrown with a service_unavailable
+ * error code.
+ *
+ * @param newName the new name of the agent.
+ * @throws XMPPException if the agent is not allowed to change his name or no response was
+ * obtained from the server.
+ */
+ public void setName(String newName) throws XMPPException {
+ AgentInfo agentInfo = new AgentInfo();
+ agentInfo.setType(IQ.Type.SET);
+ agentInfo.setTo(workgroupJID);
+ agentInfo.setFrom(getUser());
+ agentInfo.setName(newName);
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID()));
+ // Send the request
+ connection.sendPacket(agentInfo);
+
+ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java b/src/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java
new file mode 100644
index 0000000..70c95ee
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java
@@ -0,0 +1,386 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smackx.workgroup.packet.AgentStatus;
+import org.jivesoftware.smackx.workgroup.packet.AgentStatusRequest;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manges information about the agents in a workgroup and their presence.
+ *
+ * @author Matt Tucker
+ * @see AgentSession#getAgentRoster()
+ */
+public class AgentRoster {
+
+ private static final int EVENT_AGENT_ADDED = 0;
+ private static final int EVENT_AGENT_REMOVED = 1;
+ private static final int EVENT_PRESENCE_CHANGED = 2;
+
+ private Connection connection;
+ private String workgroupJID;
+ private List<String> entries;
+ private List<AgentRosterListener> listeners;
+ private Map<String, Map<String, Presence>> presenceMap;
+ // The roster is marked as initialized when at least a single roster packet
+ // has been recieved and processed.
+ boolean rosterInitialized = false;
+
+ /**
+ * Constructs a new AgentRoster.
+ *
+ * @param connection an XMPP connection.
+ */
+ AgentRoster(Connection connection, String workgroupJID) {
+ this.connection = connection;
+ this.workgroupJID = workgroupJID;
+ entries = new ArrayList<String>();
+ listeners = new ArrayList<AgentRosterListener>();
+ presenceMap = new HashMap<String, Map<String, Presence>>();
+ // Listen for any roster packets.
+ PacketFilter rosterFilter = new PacketTypeFilter(AgentStatusRequest.class);
+ connection.addPacketListener(new AgentStatusListener(), rosterFilter);
+ // Listen for any presence packets.
+ connection.addPacketListener(new PresencePacketListener(),
+ new PacketTypeFilter(Presence.class));
+
+ // Send request for roster.
+ AgentStatusRequest request = new AgentStatusRequest();
+ request.setTo(workgroupJID);
+ connection.sendPacket(request);
+ }
+
+ /**
+ * Reloads the entire roster from the server. This is an asynchronous operation,
+ * which means the method will return immediately, and the roster will be
+ * reloaded at a later point when the server responds to the reload request.
+ */
+ public void reload() {
+ AgentStatusRequest request = new AgentStatusRequest();
+ request.setTo(workgroupJID);
+ connection.sendPacket(request);
+ }
+
+ /**
+ * Adds a listener to this roster. The listener will be fired anytime one or more
+ * changes to the roster are pushed from the server.
+ *
+ * @param listener an agent roster listener.
+ */
+ public void addListener(AgentRosterListener listener) {
+ synchronized (listeners) {
+ if (!listeners.contains(listener)) {
+ listeners.add(listener);
+
+ // Fire events for the existing entries and presences in the roster
+ for (Iterator<String> it = getAgents().iterator(); it.hasNext();) {
+ String jid = it.next();
+ // Check again in case the agent is no longer in the roster (highly unlikely
+ // but possible)
+ if (entries.contains(jid)) {
+ // Fire the agent added event
+ listener.agentAdded(jid);
+ Map<String,Presence> userPresences = presenceMap.get(jid);
+ if (userPresences != null) {
+ Iterator<Presence> presences = userPresences.values().iterator();
+ while (presences.hasNext()) {
+ // Fire the presence changed event
+ listener.presenceChanged(presences.next());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from this roster. The listener will be fired anytime one or more
+ * changes to the roster are pushed from the server.
+ *
+ * @param listener a roster listener.
+ */
+ public void removeListener(AgentRosterListener listener) {
+ synchronized (listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns a count of all agents in the workgroup.
+ *
+ * @return the number of agents in the workgroup.
+ */
+ public int getAgentCount() {
+ return entries.size();
+ }
+
+ /**
+ * Returns all agents (String JID values) in the workgroup.
+ *
+ * @return all entries in the roster.
+ */
+ public Set<String> getAgents() {
+ Set<String> agents = new HashSet<String>();
+ synchronized (entries) {
+ for (Iterator<String> i = entries.iterator(); i.hasNext();) {
+ agents.add(i.next());
+ }
+ }
+ return Collections.unmodifiableSet(agents);
+ }
+
+ /**
+ * Returns true if the specified XMPP address is an agent in the workgroup.
+ *
+ * @param jid the XMPP address of the agent (eg "jsmith@example.com"). The
+ * address can be in any valid format (e.g. "domain/resource", "user@domain"
+ * or "user@domain/resource").
+ * @return true if the XMPP address is an agent in the workgroup.
+ */
+ public boolean contains(String jid) {
+ if (jid == null) {
+ return false;
+ }
+ synchronized (entries) {
+ for (Iterator<String> i = entries.iterator(); i.hasNext();) {
+ String entry = i.next();
+ if (entry.toLowerCase().equals(jid.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the presence info for a particular agent, or <tt>null</tt> if the agent
+ * is unavailable (offline) or if no presence information is available.<p>
+ *
+ * @param user a fully qualified xmpp JID. The address could be in any valid format (e.g.
+ * "domain/resource", "user@domain" or "user@domain/resource").
+ * @return the agent's current presence, or <tt>null</tt> if the agent is unavailable
+ * or if no presence information is available..
+ */
+ public Presence getPresence(String user) {
+ String key = getPresenceMapKey(user);
+ Map<String, Presence> userPresences = presenceMap.get(key);
+ if (userPresences == null) {
+ Presence presence = new Presence(Presence.Type.unavailable);
+ presence.setFrom(user);
+ return presence;
+ }
+ else {
+ // Find the resource with the highest priority
+ // Might be changed to use the resource with the highest availability instead.
+ Iterator<String> it = userPresences.keySet().iterator();
+ Presence p;
+ Presence presence = null;
+
+ while (it.hasNext()) {
+ p = (Presence)userPresences.get(it.next());
+ if (presence == null){
+ presence = p;
+ }
+ else {
+ if (p.getPriority() > presence.getPriority()) {
+ presence = p;
+ }
+ }
+ }
+ if (presence == null) {
+ presence = new Presence(Presence.Type.unavailable);
+ presence.setFrom(user);
+ return presence;
+ }
+ else {
+ return presence;
+ }
+ }
+ }
+
+ /**
+ * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
+ * can contain any valid address format such us "domain/resource", "user@domain" or
+ * "user@domain/resource". If the roster contains an entry associated with the fully qualified
+ * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
+ * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
+ * userPresences is useless since it will always contain one entry for the user.
+ *
+ * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
+ * @return the key to use in the presenceMap for the fully qualified xmpp ID.
+ */
+ private String getPresenceMapKey(String user) {
+ String key = user;
+ if (!contains(user)) {
+ key = StringUtils.parseBareAddress(user).toLowerCase();
+ }
+ return key;
+ }
+
+ /**
+ * Fires event to listeners.
+ */
+ private void fireEvent(int eventType, Object eventObject) {
+ AgentRosterListener[] listeners = null;
+ synchronized (this.listeners) {
+ listeners = new AgentRosterListener[this.listeners.size()];
+ this.listeners.toArray(listeners);
+ }
+ for (int i = 0; i < listeners.length; i++) {
+ switch (eventType) {
+ case EVENT_AGENT_ADDED:
+ listeners[i].agentAdded((String)eventObject);
+ break;
+ case EVENT_AGENT_REMOVED:
+ listeners[i].agentRemoved((String)eventObject);
+ break;
+ case EVENT_PRESENCE_CHANGED:
+ listeners[i].presenceChanged((Presence)eventObject);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Listens for all presence packets and processes them.
+ */
+ private class PresencePacketListener implements PacketListener {
+ public void processPacket(Packet packet) {
+ Presence presence = (Presence)packet;
+ String from = presence.getFrom();
+ if (from == null) {
+ // TODO Check if we need to ignore these presences or this is a server bug?
+ System.out.println("Presence with no FROM: " + presence.toXML());
+ return;
+ }
+ String key = getPresenceMapKey(from);
+
+ // If an "available" packet, add it to the presence map. Each presence map will hold
+ // for a particular user a map with the presence packets saved for each resource.
+ if (presence.getType() == Presence.Type.available) {
+ // Ignore the presence packet unless it has an agent status extension.
+ AgentStatus agentStatus = (AgentStatus)presence.getExtension(
+ AgentStatus.ELEMENT_NAME, AgentStatus.NAMESPACE);
+ if (agentStatus == null) {
+ return;
+ }
+ // Ensure that this presence is coming from an Agent of the same workgroup
+ // of this Agent
+ else if (!workgroupJID.equals(agentStatus.getWorkgroupJID())) {
+ return;
+ }
+ Map<String, Presence> userPresences;
+ // Get the user presence map
+ if (presenceMap.get(key) == null) {
+ userPresences = new HashMap<String, Presence>();
+ presenceMap.put(key, userPresences);
+ }
+ else {
+ userPresences = presenceMap.get(key);
+ }
+ // Add the new presence, using the resources as a key.
+ synchronized (userPresences) {
+ userPresences.put(StringUtils.parseResource(from), presence);
+ }
+ // Fire an event.
+ synchronized (entries) {
+ for (Iterator<String> i = entries.iterator(); i.hasNext();) {
+ String entry = i.next();
+ if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) {
+ fireEvent(EVENT_PRESENCE_CHANGED, packet);
+ }
+ }
+ }
+ }
+ // If an "unavailable" packet, remove any entries in the presence map.
+ else if (presence.getType() == Presence.Type.unavailable) {
+ if (presenceMap.get(key) != null) {
+ Map<String,Presence> userPresences = presenceMap.get(key);
+ synchronized (userPresences) {
+ userPresences.remove(StringUtils.parseResource(from));
+ }
+ if (userPresences.isEmpty()) {
+ presenceMap.remove(key);
+ }
+ }
+ // Fire an event.
+ synchronized (entries) {
+ for (Iterator<String> i = entries.iterator(); i.hasNext();) {
+ String entry = (String)i.next();
+ if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) {
+ fireEvent(EVENT_PRESENCE_CHANGED, packet);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Listens for all roster packets and processes them.
+ */
+ private class AgentStatusListener implements PacketListener {
+
+ public void processPacket(Packet packet) {
+ if (packet instanceof AgentStatusRequest) {
+ AgentStatusRequest statusRequest = (AgentStatusRequest)packet;
+ for (Iterator<AgentStatusRequest.Item> i = statusRequest.getAgents().iterator(); i.hasNext();) {
+ AgentStatusRequest.Item item = i.next();
+ String agentJID = item.getJID();
+ if ("remove".equals(item.getType())) {
+
+ // Removing the user from the roster, so remove any presence information
+ // about them.
+ String key = StringUtils.parseName(StringUtils.parseName(agentJID) + "@" +
+ StringUtils.parseServer(agentJID));
+ presenceMap.remove(key);
+ // Fire event for roster listeners.
+ fireEvent(EVENT_AGENT_REMOVED, agentJID);
+ }
+ else {
+ entries.add(agentJID);
+ // Fire event for roster listeners.
+ fireEvent(EVENT_AGENT_ADDED, agentJID);
+ }
+ }
+
+ // Mark the roster as initialized.
+ rosterInitialized = true;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java b/src/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java
new file mode 100644
index 0000000..4db9203
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java
@@ -0,0 +1,35 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smack.packet.Presence;
+
+/**
+ *
+ * @author Matt Tucker
+ */
+public interface AgentRosterListener {
+
+ public void agentAdded(String jid);
+
+ public void agentRemoved(String jid);
+
+ public void presenceChanged(Presence presence);
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/AgentSession.java b/src/org/jivesoftware/smackx/workgroup/agent/AgentSession.java
new file mode 100644
index 0000000..46d19d0
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/AgentSession.java
@@ -0,0 +1,1185 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smackx.workgroup.MetaData;
+import org.jivesoftware.smackx.workgroup.QueueUser;
+import org.jivesoftware.smackx.workgroup.WorkgroupInvitation;
+import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener;
+import org.jivesoftware.smackx.workgroup.ext.history.AgentChatHistory;
+import org.jivesoftware.smackx.workgroup.ext.history.ChatMetadata;
+import org.jivesoftware.smackx.workgroup.ext.macros.MacroGroup;
+import org.jivesoftware.smackx.workgroup.ext.macros.Macros;
+import org.jivesoftware.smackx.workgroup.ext.notes.ChatNotes;
+import org.jivesoftware.smackx.workgroup.packet.*;
+import org.jivesoftware.smackx.workgroup.settings.GenericSettings;
+import org.jivesoftware.smackx.workgroup.settings.SearchSettings;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.ReportedData;
+import org.jivesoftware.smackx.packet.MUCUser;
+
+import java.util.*;
+
+/**
+ * This class embodies the agent's active presence within a given workgroup. The application
+ * should have N instances of this class, where N is the number of workgroups to which the
+ * owning agent of the application belongs. This class provides all functionality that a
+ * session within a given workgroup is expected to have from an agent's perspective -- setting
+ * the status, tracking the status of queues to which the agent belongs within the workgroup, and
+ * dequeuing customers.
+ *
+ * @author Matt Tucker
+ * @author Derek DeMoro
+ */
+public class AgentSession {
+
+ private Connection connection;
+
+ private String workgroupJID;
+
+ private boolean online = false;
+ private Presence.Mode presenceMode;
+ private int maxChats;
+ private final Map<String, List<String>> metaData;
+
+ private Map<String, WorkgroupQueue> queues;
+
+ private final List<OfferListener> offerListeners;
+ private final List<WorkgroupInvitationListener> invitationListeners;
+ private final List<QueueUsersListener> queueUsersListeners;
+
+ private AgentRoster agentRoster = null;
+ private TranscriptManager transcriptManager;
+ private TranscriptSearchManager transcriptSearchManager;
+ private Agent agent;
+ private PacketListener packetListener;
+
+ /**
+ * Constructs a new agent session instance. Note, the {@link #setOnline(boolean)}
+ * method must be called with an argument of <tt>true</tt> to mark the agent
+ * as available to accept chat requests.
+ *
+ * @param connection a connection instance which must have already gone through
+ * authentication.
+ * @param workgroupJID the fully qualified JID of the workgroup.
+ */
+ public AgentSession(String workgroupJID, Connection connection) {
+ // Login must have been done before passing in connection.
+ if (!connection.isAuthenticated()) {
+ throw new IllegalStateException("Must login to server before creating workgroup.");
+ }
+
+ this.workgroupJID = workgroupJID;
+ this.connection = connection;
+ this.transcriptManager = new TranscriptManager(connection);
+ this.transcriptSearchManager = new TranscriptSearchManager(connection);
+
+ this.maxChats = -1;
+
+ this.metaData = new HashMap<String, List<String>>();
+
+ this.queues = new HashMap<String, WorkgroupQueue>();
+
+ offerListeners = new ArrayList<OfferListener>();
+ invitationListeners = new ArrayList<WorkgroupInvitationListener>();
+ queueUsersListeners = new ArrayList<QueueUsersListener>();
+
+ // Create a filter to listen for packets we're interested in.
+ OrFilter filter = new OrFilter();
+ filter.addFilter(new PacketTypeFilter(OfferRequestProvider.OfferRequestPacket.class));
+ filter.addFilter(new PacketTypeFilter(OfferRevokeProvider.OfferRevokePacket.class));
+ filter.addFilter(new PacketTypeFilter(Presence.class));
+ filter.addFilter(new PacketTypeFilter(Message.class));
+
+ packetListener = new PacketListener() {
+ public void processPacket(Packet packet) {
+ try {
+ handlePacket(packet);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ connection.addPacketListener(packetListener, filter);
+ // Create the agent associated to this session
+ agent = new Agent(connection, workgroupJID);
+ }
+
+ /**
+ * Close the agent session. The underlying connection will remain opened but the
+ * packet listeners that were added by this agent session will be removed.
+ */
+ public void close() {
+ connection.removePacketListener(packetListener);
+ }
+
+ /**
+ * Returns the agent roster for the workgroup, which contains
+ *
+ * @return the AgentRoster
+ */
+ public AgentRoster getAgentRoster() {
+ if (agentRoster == null) {
+ agentRoster = new AgentRoster(connection, workgroupJID);
+ }
+
+ // This might be the first time the user has asked for the roster. If so, we
+ // want to wait up to 2 seconds for the server to send back the list of agents.
+ // This behavior shields API users from having to worry about the fact that the
+ // operation is asynchronous, although they'll still have to listen for changes
+ // to the roster.
+ int elapsed = 0;
+ while (!agentRoster.rosterInitialized && elapsed <= 2000) {
+ try {
+ Thread.sleep(500);
+ }
+ catch (Exception e) {
+ // Ignore
+ }
+ elapsed += 500;
+ }
+ return agentRoster;
+ }
+
+ /**
+ * Returns the agent's current presence mode.
+ *
+ * @return the agent's current presence mode.
+ */
+ public Presence.Mode getPresenceMode() {
+ return presenceMode;
+ }
+
+ /**
+ * Returns the maximum number of chats the agent can participate in.
+ *
+ * @return the maximum number of chats the agent can participate in.
+ */
+ public int getMaxChats() {
+ return maxChats;
+ }
+
+ /**
+ * Returns true if the agent is online with the workgroup.
+ *
+ * @return true if the agent is online with the workgroup.
+ */
+ public boolean isOnline() {
+ return online;
+ }
+
+ /**
+ * Allows the addition of a new key-value pair to the agent's meta data, if the value is
+ * new data, the revised meta data will be rebroadcast in an agent's presence broadcast.
+ *
+ * @param key the meta data key
+ * @param val the non-null meta data value
+ * @throws XMPPException if an exception occurs.
+ */
+ public void setMetaData(String key, String val) throws XMPPException {
+ synchronized (this.metaData) {
+ List<String> oldVals = metaData.get(key);
+
+ if ((oldVals == null) || (!oldVals.get(0).equals(val))) {
+ oldVals.set(0, val);
+
+ setStatus(presenceMode, maxChats);
+ }
+ }
+ }
+
+ /**
+ * Allows the removal of data from the agent's meta data, if the key represents existing data,
+ * the revised meta data will be rebroadcast in an agent's presence broadcast.
+ *
+ * @param key the meta data key.
+ * @throws XMPPException if an exception occurs.
+ */
+ public void removeMetaData(String key) throws XMPPException {
+ synchronized (this.metaData) {
+ List<String> oldVal = metaData.remove(key);
+
+ if (oldVal != null) {
+ setStatus(presenceMode, maxChats);
+ }
+ }
+ }
+
+ /**
+ * Allows the retrieval of meta data for a specified key.
+ *
+ * @param key the meta data key
+ * @return the meta data value associated with the key or <tt>null</tt> if the meta-data
+ * doesn't exist..
+ */
+ public List<String> getMetaData(String key) {
+ return metaData.get(key);
+ }
+
+ /**
+ * Sets whether the agent is online with the workgroup. If the user tries to go online with
+ * the workgroup but is not allowed to be an agent, an XMPPError with error code 401 will
+ * be thrown.
+ *
+ * @param online true to set the agent as online with the workgroup.
+ * @throws XMPPException if an error occurs setting the online status.
+ */
+ public void setOnline(boolean online) throws XMPPException {
+ // If the online status hasn't changed, do nothing.
+ if (this.online == online) {
+ return;
+ }
+
+ Presence presence;
+
+ // If the user is going online...
+ if (online) {
+ presence = new Presence(Presence.Type.available);
+ presence.setTo(workgroupJID);
+ presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME,
+ AgentStatus.NAMESPACE));
+
+ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID)));
+
+ connection.sendPacket(presence);
+
+ presence = (Presence)collector.nextResult(5000);
+ collector.cancel();
+ if (!presence.isAvailable()) {
+ throw new XMPPException("No response from server on status set.");
+ }
+
+ if (presence.getError() != null) {
+ throw new XMPPException(presence.getError());
+ }
+
+ // We can safely update this iv since we didn't get any error
+ this.online = online;
+ }
+ // Otherwise the user is going offline...
+ else {
+ // Update this iv now since we don't care at this point of any error
+ this.online = online;
+
+ presence = new Presence(Presence.Type.unavailable);
+ presence.setTo(workgroupJID);
+ presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME,
+ AgentStatus.NAMESPACE));
+ connection.sendPacket(presence);
+ }
+ }
+
+ /**
+ * Sets the agent's current status with the workgroup. The presence mode affects
+ * how offers are routed to the agent. The possible presence modes with their
+ * meanings are as follows:<ul>
+ * <p/>
+ * <li>Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats
+ * (equivalent to Presence.Mode.CHAT).
+ * <li>Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed.
+ * However, special case, or extreme urgency chats may still be offered to the agent.
+ * <li>Presence.Mode.AWAY -- the agent is not available and should not
+ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).</ul>
+ * <p/>
+ * The max chats value is the maximum number of chats the agent is willing to have
+ * routed to them at once. Some servers may be configured to only accept max chat
+ * values in a certain range; for example, between two and five. In that case, the
+ * maxChats value the agent sends may be adjusted by the server to a value within that
+ * range.
+ *
+ * @param presenceMode the presence mode of the agent.
+ * @param maxChats the maximum number of chats the agent is willing to accept.
+ * @throws XMPPException if an error occurs setting the agent status.
+ * @throws IllegalStateException if the agent is not online with the workgroup.
+ */
+ public void setStatus(Presence.Mode presenceMode, int maxChats) throws XMPPException {
+ setStatus(presenceMode, maxChats, null);
+ }
+
+ /**
+ * Sets the agent's current status with the workgroup. The presence mode affects how offers
+ * are routed to the agent. The possible presence modes with their meanings are as follows:<ul>
+ * <p/>
+ * <li>Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats
+ * (equivalent to Presence.Mode.CHAT).
+ * <li>Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed.
+ * However, special case, or extreme urgency chats may still be offered to the agent.
+ * <li>Presence.Mode.AWAY -- the agent is not available and should not
+ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).</ul>
+ * <p/>
+ * The max chats value is the maximum number of chats the agent is willing to have routed to
+ * them at once. Some servers may be configured to only accept max chat values in a certain
+ * range; for example, between two and five. In that case, the maxChats value the agent sends
+ * may be adjusted by the server to a value within that range.
+ *
+ * @param presenceMode the presence mode of the agent.
+ * @param maxChats the maximum number of chats the agent is willing to accept.
+ * @param status sets the status message of the presence update.
+ * @throws XMPPException if an error occurs setting the agent status.
+ * @throws IllegalStateException if the agent is not online with the workgroup.
+ */
+ public void setStatus(Presence.Mode presenceMode, int maxChats, String status)
+ throws XMPPException {
+ if (!online) {
+ throw new IllegalStateException("Cannot set status when the agent is not online.");
+ }
+
+ if (presenceMode == null) {
+ presenceMode = Presence.Mode.available;
+ }
+ this.presenceMode = presenceMode;
+ this.maxChats = maxChats;
+
+ Presence presence = new Presence(Presence.Type.available);
+ presence.setMode(presenceMode);
+ presence.setTo(this.getWorkgroupJID());
+
+ if (status != null) {
+ presence.setStatus(status);
+ }
+ // Send information about max chats and current chats as a packet extension.
+ DefaultPacketExtension agentStatus = new DefaultPacketExtension(AgentStatus.ELEMENT_NAME,
+ AgentStatus.NAMESPACE);
+ agentStatus.setValue("max-chats", "" + maxChats);
+ presence.addExtension(agentStatus);
+ presence.addExtension(new MetaData(this.metaData));
+
+ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID)));
+
+ this.connection.sendPacket(presence);
+
+ presence = (Presence)collector.nextResult(5000);
+ collector.cancel();
+ if (!presence.isAvailable()) {
+ throw new XMPPException("No response from server on status set.");
+ }
+
+ if (presence.getError() != null) {
+ throw new XMPPException(presence.getError());
+ }
+ }
+
+ /**
+ * Sets the agent's current status with the workgroup. The presence mode affects how offers
+ * are routed to the agent. The possible presence modes with their meanings are as follows:<ul>
+ * <p/>
+ * <li>Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats
+ * (equivalent to Presence.Mode.CHAT).
+ * <li>Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed.
+ * However, special case, or extreme urgency chats may still be offered to the agent.
+ * <li>Presence.Mode.AWAY -- the agent is not available and should not
+ * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).</ul>
+ *
+ * @param presenceMode the presence mode of the agent.
+ * @param status sets the status message of the presence update.
+ * @throws XMPPException if an error occurs setting the agent status.
+ * @throws IllegalStateException if the agent is not online with the workgroup.
+ */
+ public void setStatus(Presence.Mode presenceMode, String status) throws XMPPException {
+ if (!online) {
+ throw new IllegalStateException("Cannot set status when the agent is not online.");
+ }
+
+ if (presenceMode == null) {
+ presenceMode = Presence.Mode.available;
+ }
+ this.presenceMode = presenceMode;
+
+ Presence presence = new Presence(Presence.Type.available);
+ presence.setMode(presenceMode);
+ presence.setTo(this.getWorkgroupJID());
+
+ if (status != null) {
+ presence.setStatus(status);
+ }
+ presence.addExtension(new MetaData(this.metaData));
+
+ PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class),
+ new FromContainsFilter(workgroupJID)));
+
+ this.connection.sendPacket(presence);
+
+ presence = (Presence)collector.nextResult(5000);
+ collector.cancel();
+ if (!presence.isAvailable()) {
+ throw new XMPPException("No response from server on status set.");
+ }
+
+ if (presence.getError() != null) {
+ throw new XMPPException(presence.getError());
+ }
+ }
+
+ /**
+ * Removes a user from the workgroup queue. This is an administrative action that the
+ * <p/>
+ * The agent is not guaranteed of having privileges to perform this action; an exception
+ * denying the request may be thrown.
+ *
+ * @param userID the ID of the user to remove.
+ * @throws XMPPException if an exception occurs.
+ */
+ public void dequeueUser(String userID) throws XMPPException {
+ // todo: this method simply won't work right now.
+ DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
+
+ // PENDING
+ this.connection.sendPacket(departPacket);
+ }
+
+ /**
+ * Returns the transcripts of a given user. The answer will contain the complete history of
+ * conversations that a user had.
+ *
+ * @param userID the id of the user to get his conversations.
+ * @return the transcripts of a given user.
+ * @throws XMPPException if an error occurs while getting the information.
+ */
+ public Transcripts getTranscripts(String userID) throws XMPPException {
+ return transcriptManager.getTranscripts(workgroupJID, userID);
+ }
+
+ /**
+ * Returns the full conversation transcript of a given session.
+ *
+ * @param sessionID the id of the session to get the full transcript.
+ * @return the full conversation transcript of a given session.
+ * @throws XMPPException if an error occurs while getting the information.
+ */
+ public Transcript getTranscript(String sessionID) throws XMPPException {
+ return transcriptManager.getTranscript(workgroupJID, sessionID);
+ }
+
+ /**
+ * Returns the Form to use for searching transcripts. It is unlikely that the server
+ * will change the form (without a restart) so it is safe to keep the returned form
+ * for future submissions.
+ *
+ * @return the Form to use for searching transcripts.
+ * @throws XMPPException if an error occurs while sending the request to the server.
+ */
+ public Form getTranscriptSearchForm() throws XMPPException {
+ return transcriptSearchManager.getSearchForm(StringUtils.parseServer(workgroupJID));
+ }
+
+ /**
+ * Submits the completed form and returns the result of the transcript search. The result
+ * will include all the data returned from the server so be careful with the amount of
+ * data that the search may return.
+ *
+ * @param completedForm the filled out search form.
+ * @return the result of the transcript search.
+ * @throws XMPPException if an error occurs while submiting the search to the server.
+ */
+ public ReportedData searchTranscripts(Form completedForm) throws XMPPException {
+ return transcriptSearchManager.submitSearch(StringUtils.parseServer(workgroupJID),
+ completedForm);
+ }
+
+ /**
+ * Asks the workgroup for information about the occupants of the specified room. The returned
+ * information will include the real JID of the occupants, the nickname of the user in the
+ * room as well as the date when the user joined the room.
+ *
+ * @param roomID the room to get information about its occupants.
+ * @return information about the occupants of the specified room.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public OccupantsInfo getOccupantsInfo(String roomID) throws XMPPException {
+ OccupantsInfo request = new OccupantsInfo(roomID);
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+ OccupantsInfo response = (OccupantsInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+
+ /**
+ * @return the fully-qualified name of the workgroup for which this session exists
+ */
+ public String getWorkgroupJID() {
+ return workgroupJID;
+ }
+
+ /**
+ * Returns the Agent associated to this session.
+ *
+ * @return the Agent associated to this session.
+ */
+ public Agent getAgent() {
+ return agent;
+ }
+
+ /**
+ * @param queueName the name of the queue
+ * @return an instance of WorkgroupQueue for the argument queue name, or null if none exists
+ */
+ public WorkgroupQueue getQueue(String queueName) {
+ return queues.get(queueName);
+ }
+
+ public Iterator<WorkgroupQueue> getQueues() {
+ return Collections.unmodifiableMap((new HashMap<String, WorkgroupQueue>(queues))).values().iterator();
+ }
+
+ public void addQueueUsersListener(QueueUsersListener listener) {
+ synchronized (queueUsersListeners) {
+ if (!queueUsersListeners.contains(listener)) {
+ queueUsersListeners.add(listener);
+ }
+ }
+ }
+
+ public void removeQueueUsersListener(QueueUsersListener listener) {
+ synchronized (queueUsersListeners) {
+ queueUsersListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Adds an offer listener.
+ *
+ * @param offerListener the offer listener.
+ */
+ public void addOfferListener(OfferListener offerListener) {
+ synchronized (offerListeners) {
+ if (!offerListeners.contains(offerListener)) {
+ offerListeners.add(offerListener);
+ }
+ }
+ }
+
+ /**
+ * Removes an offer listener.
+ *
+ * @param offerListener the offer listener.
+ */
+ public void removeOfferListener(OfferListener offerListener) {
+ synchronized (offerListeners) {
+ offerListeners.remove(offerListener);
+ }
+ }
+
+ /**
+ * Adds an invitation listener.
+ *
+ * @param invitationListener the invitation listener.
+ */
+ public void addInvitationListener(WorkgroupInvitationListener invitationListener) {
+ synchronized (invitationListeners) {
+ if (!invitationListeners.contains(invitationListener)) {
+ invitationListeners.add(invitationListener);
+ }
+ }
+ }
+
+ /**
+ * Removes an invitation listener.
+ *
+ * @param invitationListener the invitation listener.
+ */
+ public void removeInvitationListener(WorkgroupInvitationListener invitationListener) {
+ synchronized (invitationListeners) {
+ invitationListeners.remove(invitationListener);
+ }
+ }
+
+ private void fireOfferRequestEvent(OfferRequestProvider.OfferRequestPacket requestPacket) {
+ Offer offer = new Offer(this.connection, this, requestPacket.getUserID(),
+ requestPacket.getUserJID(), this.getWorkgroupJID(),
+ new Date((new Date()).getTime() + (requestPacket.getTimeout() * 1000)),
+ requestPacket.getSessionID(), requestPacket.getMetaData(), requestPacket.getContent());
+
+ synchronized (offerListeners) {
+ for (OfferListener listener : offerListeners) {
+ listener.offerReceived(offer);
+ }
+ }
+ }
+
+ private void fireOfferRevokeEvent(OfferRevokeProvider.OfferRevokePacket orp) {
+ RevokedOffer revokedOffer = new RevokedOffer(orp.getUserJID(), orp.getUserID(),
+ this.getWorkgroupJID(), orp.getSessionID(), orp.getReason(), new Date());
+
+ synchronized (offerListeners) {
+ for (OfferListener listener : offerListeners) {
+ listener.offerRevoked(revokedOffer);
+ }
+ }
+ }
+
+ private void fireInvitationEvent(String groupChatJID, String sessionID, String body,
+ String from, Map<String, List<String>> metaData) {
+ WorkgroupInvitation invitation = new WorkgroupInvitation(connection.getUser(), groupChatJID,
+ workgroupJID, sessionID, body, from, metaData);
+
+ synchronized (invitationListeners) {
+ for (WorkgroupInvitationListener listener : invitationListeners) {
+ listener.invitationReceived(invitation);
+ }
+ }
+ }
+
+ private void fireQueueUsersEvent(WorkgroupQueue queue, WorkgroupQueue.Status status,
+ int averageWaitTime, Date oldestEntry, Set<QueueUser> users) {
+ synchronized (queueUsersListeners) {
+ for (QueueUsersListener listener : queueUsersListeners) {
+ if (status != null) {
+ listener.statusUpdated(queue, status);
+ }
+ if (averageWaitTime != -1) {
+ listener.averageWaitTimeUpdated(queue, averageWaitTime);
+ }
+ if (oldestEntry != null) {
+ listener.oldestEntryUpdated(queue, oldestEntry);
+ }
+ if (users != null) {
+ listener.usersUpdated(queue, users);
+ }
+ }
+ }
+ }
+
+ // PacketListener Implementation.
+
+ private void handlePacket(Packet packet) {
+ if (packet instanceof OfferRequestProvider.OfferRequestPacket) {
+ // Acknowledge the IQ set.
+ IQ reply = new IQ() {
+ public String getChildElementXML() {
+ return null;
+ }
+ };
+ reply.setPacketID(packet.getPacketID());
+ reply.setTo(packet.getFrom());
+ reply.setType(IQ.Type.RESULT);
+ connection.sendPacket(reply);
+
+ fireOfferRequestEvent((OfferRequestProvider.OfferRequestPacket)packet);
+ }
+ else if (packet instanceof Presence) {
+ Presence presence = (Presence)packet;
+
+ // The workgroup can send us a number of different presence packets. We
+ // check for different packet extensions to see what type of presence
+ // packet it is.
+
+ String queueName = StringUtils.parseResource(presence.getFrom());
+ WorkgroupQueue queue = queues.get(queueName);
+ // If there isn't already an entry for the queue, create a new one.
+ if (queue == null) {
+ queue = new WorkgroupQueue(queueName);
+ queues.put(queueName, queue);
+ }
+
+ // QueueOverview packet extensions contain basic information about a queue.
+ QueueOverview queueOverview = (QueueOverview)presence.getExtension(QueueOverview.ELEMENT_NAME, QueueOverview.NAMESPACE);
+ if (queueOverview != null) {
+ if (queueOverview.getStatus() == null) {
+ queue.setStatus(WorkgroupQueue.Status.CLOSED);
+ }
+ else {
+ queue.setStatus(queueOverview.getStatus());
+ }
+ queue.setAverageWaitTime(queueOverview.getAverageWaitTime());
+ queue.setOldestEntry(queueOverview.getOldestEntry());
+ // Fire event.
+ fireQueueUsersEvent(queue, queueOverview.getStatus(),
+ queueOverview.getAverageWaitTime(), queueOverview.getOldestEntry(),
+ null);
+ return;
+ }
+
+ // QueueDetails packet extensions contain information about the users in
+ // a queue.
+ QueueDetails queueDetails = (QueueDetails)packet.getExtension(QueueDetails.ELEMENT_NAME, QueueDetails.NAMESPACE);
+ if (queueDetails != null) {
+ queue.setUsers(queueDetails.getUsers());
+ // Fire event.
+ fireQueueUsersEvent(queue, null, -1, null, queueDetails.getUsers());
+ return;
+ }
+
+ // Notify agent packets gives an overview of agent activity in a queue.
+ DefaultPacketExtension notifyAgents = (DefaultPacketExtension)presence.getExtension("notify-agents", "http://jabber.org/protocol/workgroup");
+ if (notifyAgents != null) {
+ int currentChats = Integer.parseInt(notifyAgents.getValue("current-chats"));
+ int maxChats = Integer.parseInt(notifyAgents.getValue("max-chats"));
+ queue.setCurrentChats(currentChats);
+ queue.setMaxChats(maxChats);
+ // Fire event.
+ // TODO: might need another event for current chats and max chats of queue
+ return;
+ }
+ }
+ else if (packet instanceof Message) {
+ Message message = (Message)packet;
+
+ // Check if a room invitation was sent and if the sender is the workgroup
+ MUCUser mucUser = (MUCUser)message.getExtension("x",
+ "http://jabber.org/protocol/muc#user");
+ MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null;
+ if (invite != null && workgroupJID.equals(invite.getFrom())) {
+ String sessionID = null;
+ Map<String, List<String>> metaData = null;
+
+ SessionID sessionIDExt = (SessionID)message.getExtension(SessionID.ELEMENT_NAME,
+ SessionID.NAMESPACE);
+ if (sessionIDExt != null) {
+ sessionID = sessionIDExt.getSessionID();
+ }
+
+ MetaData metaDataExt = (MetaData)message.getExtension(MetaData.ELEMENT_NAME,
+ MetaData.NAMESPACE);
+ if (metaDataExt != null) {
+ metaData = metaDataExt.getMetaData();
+ }
+
+ this.fireInvitationEvent(message.getFrom(), sessionID, message.getBody(),
+ message.getFrom(), metaData);
+ }
+ }
+ else if (packet instanceof OfferRevokeProvider.OfferRevokePacket) {
+ // Acknowledge the IQ set.
+ IQ reply = new IQ() {
+ public String getChildElementXML() {
+ return null;
+ }
+ };
+ reply.setPacketID(packet.getPacketID());
+ reply.setType(IQ.Type.RESULT);
+ connection.sendPacket(reply);
+
+ fireOfferRevokeEvent((OfferRevokeProvider.OfferRevokePacket)packet);
+ }
+ }
+
+ /**
+ * Creates a ChatNote that will be mapped to the given chat session.
+ *
+ * @param sessionID the session id of a Chat Session.
+ * @param note the chat note to add.
+ * @throws XMPPException is thrown if an error occurs while adding the note.
+ */
+ public void setNote(String sessionID, String note) throws XMPPException {
+ note = ChatNotes.replace(note, "\n", "\\n");
+ note = StringUtils.escapeForXML(note);
+
+ ChatNotes notes = new ChatNotes();
+ notes.setType(IQ.Type.SET);
+ notes.setTo(workgroupJID);
+ notes.setSessionID(sessionID);
+ notes.setNotes(note);
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(notes.getPacketID()));
+ // Send the request
+ connection.sendPacket(notes);
+
+ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Retrieves the ChatNote associated with a given chat session.
+ *
+ * @param sessionID the sessionID of the chat session.
+ * @return the <code>ChatNote</code> associated with a given chat session.
+ * @throws XMPPException if an error occurs while retrieving the ChatNote.
+ */
+ public ChatNotes getNote(String sessionID) throws XMPPException {
+ ChatNotes request = new ChatNotes();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+ request.setSessionID(sessionID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+ ChatNotes response = (ChatNotes)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+
+ }
+
+ /**
+ * Retrieves the AgentChatHistory associated with a particular agent jid.
+ *
+ * @param jid the jid of the agent.
+ * @param maxSessions the max number of sessions to retrieve.
+ * @param startDate the starting date of sessions to retrieve.
+ * @return the chat history associated with a given jid.
+ * @throws XMPPException if an error occurs while retrieving the AgentChatHistory.
+ */
+ public AgentChatHistory getAgentHistory(String jid, int maxSessions, Date startDate) throws XMPPException {
+ AgentChatHistory request;
+ if (startDate != null) {
+ request = new AgentChatHistory(jid, maxSessions, startDate);
+ }
+ else {
+ request = new AgentChatHistory(jid, maxSessions);
+ }
+
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+ AgentChatHistory response = (AgentChatHistory)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+
+ /**
+ * Asks the workgroup for it's Search Settings.
+ *
+ * @return SearchSettings the search settings for this workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public SearchSettings getSearchSettings() throws XMPPException {
+ SearchSettings request = new SearchSettings();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ SearchSettings response = (SearchSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+
+ /**
+ * Asks the workgroup for it's Global Macros.
+ *
+ * @param global true to retrieve global macros, otherwise false for personal macros.
+ * @return MacroGroup the root macro group.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public MacroGroup getMacros(boolean global) throws XMPPException {
+ Macros request = new Macros();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+ request.setPersonal(!global);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ Macros response = (Macros)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response.getRootGroup();
+ }
+
+ /**
+ * Persists the Personal Macro for an agent.
+ *
+ * @param group the macro group to save.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public void saveMacros(MacroGroup group) throws XMPPException {
+ Macros request = new Macros();
+ request.setType(IQ.Type.SET);
+ request.setTo(workgroupJID);
+ request.setPersonal(true);
+ request.setPersonalMacroGroup(group);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Query for metadata associated with a session id.
+ *
+ * @param sessionID the sessionID to query for.
+ * @return Map a map of all metadata associated with the sessionID.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public Map<String, List<String>> getChatMetadata(String sessionID) throws XMPPException {
+ ChatMetadata request = new ChatMetadata();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+ request.setSessionID(sessionID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ ChatMetadata response = (ChatMetadata)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response.getMetadata();
+ }
+
+ /**
+ * Invites a user or agent to an existing session support. The provided invitee's JID can be of
+ * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service
+ * will decide the best agent to receive the invitation.<p>
+ *
+ * This method will return either when the service returned an ACK of the request or if an error occured
+ * while requesting the invitation. After sending the ACK the service will send the invitation to the target
+ * entity. When dealing with agents the common sequence of offer-response will be followed. However, when
+ * sending an invitation to a user a standard MUC invitation will be sent.<p>
+ *
+ * The agent or user that accepted the offer <b>MUST</b> join the room. Failing to do so will make
+ * the invitation to fail. The inviter will eventually receive a message error indicating that the invitee
+ * accepted the offer but failed to join the room.
+ *
+ * Different situations may lead to a failed invitation. Possible cases are: 1) all agents rejected the
+ * offer and ther are no agents available, 2) the agent that accepted the offer failed to join the room or
+ * 2) the user that received the MUC invitation never replied or joined the room. In any of these cases
+ * (or other failing cases) the inviter will get an error message with the failed notification.
+ *
+ * @param type type of entity that will get the invitation.
+ * @param invitee JID of entity that will get the invitation.
+ * @param sessionID ID of the support session that the invitee is being invited.
+ * @param reason the reason of the invitation.
+ * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process
+ * the request.
+ */
+ public void sendRoomInvitation(RoomInvitation.Type type, String invitee, String sessionID, String reason)
+ throws XMPPException {
+ final RoomInvitation invitation = new RoomInvitation(type, invitee, sessionID, reason);
+ IQ iq = new IQ() {
+
+ public String getChildElementXML() {
+ return invitation.toXML();
+ }
+ };
+ iq.setType(IQ.Type.SET);
+ iq.setTo(workgroupJID);
+ iq.setFrom(connection.getUser());
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
+ connection.sendPacket(iq);
+
+ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Transfer an existing session support to another user or agent. The provided invitee's JID can be of
+ * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service
+ * will decide the best agent to receive the invitation.<p>
+ *
+ * This method will return either when the service returned an ACK of the request or if an error occured
+ * while requesting the transfer. After sending the ACK the service will send the invitation to the target
+ * entity. When dealing with agents the common sequence of offer-response will be followed. However, when
+ * sending an invitation to a user a standard MUC invitation will be sent.<p>
+ *
+ * Once the invitee joins the support room the workgroup service will kick the inviter from the room.<p>
+ *
+ * Different situations may lead to a failed transfers. Possible cases are: 1) all agents rejected the
+ * offer and there are no agents available, 2) the agent that accepted the offer failed to join the room
+ * or 2) the user that received the MUC invitation never replied or joined the room. In any of these cases
+ * (or other failing cases) the inviter will get an error message with the failed notification.
+ *
+ * @param type type of entity that will get the invitation.
+ * @param invitee JID of entity that will get the invitation.
+ * @param sessionID ID of the support session that the invitee is being invited.
+ * @param reason the reason of the invitation.
+ * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process
+ * the request.
+ */
+ public void sendRoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason)
+ throws XMPPException {
+ final RoomTransfer transfer = new RoomTransfer(type, invitee, sessionID, reason);
+ IQ iq = new IQ() {
+
+ public String getChildElementXML() {
+ return transfer.toXML();
+ }
+ };
+ iq.setType(IQ.Type.SET);
+ iq.setTo(workgroupJID);
+ iq.setFrom(connection.getUser());
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
+ connection.sendPacket(iq);
+
+ IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Returns the generic metadata of the workgroup the agent belongs to.
+ *
+ * @param con the Connection to use.
+ * @param query an optional query object used to tell the server what metadata to retrieve. This can be null.
+ * @throws XMPPException if an error occurs while sending the request to the server.
+ * @return the settings for the workgroup.
+ */
+ public GenericSettings getGenericSettings(Connection con, String query) throws XMPPException {
+ GenericSettings setting = new GenericSettings();
+ setting.setType(IQ.Type.GET);
+ setting.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(setting.getPacketID()));
+ connection.sendPacket(setting);
+
+ GenericSettings response = (GenericSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+
+ public boolean hasMonitorPrivileges(Connection con) throws XMPPException {
+ MonitorPacket request = new MonitorPacket();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+ MonitorPacket response = (MonitorPacket)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response.isMonitor();
+
+ }
+
+ public void makeRoomOwner(Connection con, String sessionID) throws XMPPException {
+ MonitorPacket request = new MonitorPacket();
+ request.setType(IQ.Type.SET);
+ request.setTo(workgroupJID);
+ request.setSessionID(sessionID);
+
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+ Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java b/src/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java
new file mode 100644
index 0000000..16b324a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java
@@ -0,0 +1,62 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+/**
+ * Request sent by an agent to invite another agent or user.
+ *
+ * @author Gaston Dombiak
+ */
+public class InvitationRequest extends OfferContent {
+
+ private String inviter;
+ private String room;
+ private String reason;
+
+ public InvitationRequest(String inviter, String room, String reason) {
+ this.inviter = inviter;
+ this.room = room;
+ this.reason = reason;
+ }
+
+ public String getInviter() {
+ return inviter;
+ }
+
+ public String getRoom() {
+ return room;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ boolean isUserRequest() {
+ return false;
+ }
+
+ boolean isInvitation() {
+ return true;
+ }
+
+ boolean isTransfer() {
+ return false;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/Offer.java b/src/org/jivesoftware/smackx/workgroup/agent/Offer.java
new file mode 100644
index 0000000..ece8c6f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/Offer.java
@@ -0,0 +1,223 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class embodying the semantic agent chat offer; specific instances allow the acceptance or
+ * rejecting of the offer.<br>
+ *
+ * @author Matt Tucker
+ * @author loki der quaeler
+ * @author Derek DeMoro
+ */
+public class Offer {
+
+ private Connection connection;
+ private AgentSession session;
+
+ private String sessionID;
+ private String userJID;
+ private String userID;
+ private String workgroupName;
+ private Date expiresDate;
+ private Map<String, List<String>> metaData;
+ private OfferContent content;
+
+ private boolean accepted = false;
+ private boolean rejected = false;
+
+ /**
+ * Creates a new offer.
+ *
+ * @param conn the XMPP connection with which the issuing session was created.
+ * @param agentSession the agent session instance through which this offer was issued.
+ * @param userID the userID of the user from which the offer originates.
+ * @param userJID the XMPP address of the user from which the offer originates.
+ * @param workgroupName the fully qualified name of the workgroup.
+ * @param expiresDate the date at which this offer expires.
+ * @param sessionID the session id associated with the offer.
+ * @param metaData the metadata associated with the offer.
+ * @param content content of the offer. The content explains the reason for the offer
+ * (e.g. user request, transfer)
+ */
+ Offer(Connection conn, AgentSession agentSession, String userID,
+ String userJID, String workgroupName, Date expiresDate,
+ String sessionID, Map<String, List<String>> metaData, OfferContent content)
+ {
+ this.connection = conn;
+ this.session = agentSession;
+ this.userID = userID;
+ this.userJID = userJID;
+ this.workgroupName = workgroupName;
+ this.expiresDate = expiresDate;
+ this.sessionID = sessionID;
+ this.metaData = metaData;
+ this.content = content;
+ }
+
+ /**
+ * Accepts the offer.
+ */
+ public void accept() {
+ Packet acceptPacket = new AcceptPacket(this.session.getWorkgroupJID());
+ connection.sendPacket(acceptPacket);
+ // TODO: listen for a reply.
+ accepted = true;
+ }
+
+ /**
+ * Rejects the offer.
+ */
+ public void reject() {
+ RejectPacket rejectPacket = new RejectPacket(this.session.getWorkgroupJID());
+ connection.sendPacket(rejectPacket);
+ // TODO: listen for a reply.
+ rejected = true;
+ }
+
+ /**
+ * Returns the userID that the offer originates from. In most cases, the
+ * userID will simply be the JID of the requesting user. However, users can
+ * also manually specify a userID for their request. In that case, that value will
+ * be returned.
+ *
+ * @return the userID of the user from which the offer originates.
+ */
+ public String getUserID() {
+ return userID;
+ }
+
+ /**
+ * Returns the JID of the user that made the offer request.
+ *
+ * @return the user's JID.
+ */
+ public String getUserJID() {
+ return userJID;
+ }
+
+ /**
+ * The fully qualified name of the workgroup (eg support@example.com).
+ *
+ * @return the name of the workgroup.
+ */
+ public String getWorkgroupName() {
+ return this.workgroupName;
+ }
+
+ /**
+ * The date when the offer will expire. The agent must {@link #accept()}
+ * the offer before the expiration date or the offer will lapse and be
+ * routed to another agent. Alternatively, the agent can {@link #reject()}
+ * the offer at any time if they don't wish to accept it..
+ *
+ * @return the date at which this offer expires.
+ */
+ public Date getExpiresDate() {
+ return this.expiresDate;
+ }
+
+ /**
+ * The session ID associated with the offer.
+ *
+ * @return the session id associated with the offer.
+ */
+ public String getSessionID() {
+ return this.sessionID;
+ }
+
+ /**
+ * The meta-data associated with the offer.
+ *
+ * @return the offer meta-data.
+ */
+ public Map<String, List<String>> getMetaData() {
+ return this.metaData;
+ }
+
+ /**
+ * Returns the content of the offer. The content explains the reason for the offer
+ * (e.g. user request, transfer)
+ *
+ * @return the content of the offer.
+ */
+ public OfferContent getContent() {
+ return content;
+ }
+
+ /**
+ * Returns true if the agent accepted this offer.
+ *
+ * @return true if the agent accepted this offer.
+ */
+ public boolean isAccepted() {
+ return accepted;
+ }
+
+ /**
+ * Return true if the agent rejected this offer.
+ *
+ * @return true if the agent rejected this offer.
+ */
+ public boolean isRejected() {
+ return rejected;
+ }
+
+ /**
+ * Packet for rejecting offers.
+ */
+ private class RejectPacket extends IQ {
+
+ RejectPacket(String workgroup) {
+ this.setTo(workgroup);
+ this.setType(IQ.Type.SET);
+ }
+
+ public String getChildElementXML() {
+ return "<offer-reject id=\"" + Offer.this.getSessionID() +
+ "\" xmlns=\"http://jabber.org/protocol/workgroup" + "\"/>";
+ }
+ }
+
+ /**
+ * Packet for accepting an offer.
+ */
+ private class AcceptPacket extends IQ {
+
+ AcceptPacket(String workgroup) {
+ this.setTo(workgroup);
+ this.setType(IQ.Type.SET);
+ }
+
+ public String getChildElementXML() {
+ return "<offer-accept id=\"" + Offer.this.getSessionID() +
+ "\" xmlns=\"http://jabber.org/protocol/workgroup" + "\"/>";
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java b/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java
new file mode 100644
index 0000000..f55d588
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java
@@ -0,0 +1,114 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+
+public class OfferConfirmation extends IQ {
+ private String userJID;
+ private long sessionID;
+
+ public String getUserJID() {
+ return userJID;
+ }
+
+ public void setUserJID(String userJID) {
+ this.userJID = userJID;
+ }
+
+ public long getSessionID() {
+ return sessionID;
+ }
+
+ public void setSessionID(long sessionID) {
+ this.sessionID = sessionID;
+ }
+
+
+ public void notifyService(Connection con, String workgroup, String createdRoomName) {
+ NotifyServicePacket packet = new NotifyServicePacket(workgroup, createdRoomName);
+ con.sendPacket(packet);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<offer-confirmation xmlns=\"http://jabber.org/protocol/workgroup\">");
+ buf.append("</offer-confirmation>");
+ return buf.toString();
+ }
+
+ public static class Provider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ final OfferConfirmation confirmation = new OfferConfirmation();
+
+ boolean done = false;
+ while (!done) {
+ parser.next();
+ String elementName = parser.getName();
+ if (parser.getEventType() == XmlPullParser.START_TAG && "user-jid".equals(elementName)) {
+ try {
+ confirmation.setUserJID(parser.nextText());
+ }
+ catch (NumberFormatException nfe) {
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.START_TAG && "session-id".equals(elementName)) {
+ try {
+ confirmation.setSessionID(Long.valueOf(parser.nextText()));
+ }
+ catch (NumberFormatException nfe) {
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.END_TAG && "offer-confirmation".equals(elementName)) {
+ done = true;
+ }
+ }
+
+
+ return confirmation;
+ }
+ }
+
+
+ /**
+ * Packet for notifying server of RoomName
+ */
+ private class NotifyServicePacket extends IQ {
+ String roomName;
+
+ NotifyServicePacket(String workgroup, String roomName) {
+ this.setTo(workgroup);
+ this.setType(IQ.Type.RESULT);
+
+ this.roomName = roomName;
+ }
+
+ public String getChildElementXML() {
+ return "<offer-confirmation roomname=\"" + roomName + "\" xmlns=\"http://jabber.org/protocol/workgroup" + "\"/>";
+ }
+ }
+
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java b/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java
new file mode 100644
index 0000000..fb10550
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java
@@ -0,0 +1,32 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+public interface OfferConfirmationListener {
+
+
+ /**
+ * The implementing class instance will be notified via this when the AgentSession has confirmed
+ * the acceptance of the <code>Offer</code>. The instance will then have the ability to create the room and
+ * send the service the room name created for tracking.
+ *
+ * @param confirmedOffer the ConfirmedOffer
+ */
+ void offerConfirmed(OfferConfirmation confirmedOffer);
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/OfferContent.java b/src/org/jivesoftware/smackx/workgroup/agent/OfferContent.java
new file mode 100644
index 0000000..a11ddc3
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/OfferContent.java
@@ -0,0 +1,55 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+/**
+ * Type of content being included in the offer. The content actually explains the reason
+ * the agent is getting an offer.
+ *
+ * @author Gaston Dombiak
+ */
+public abstract class OfferContent {
+
+ /**
+ * Returns true if the content of the offer is related to a user request. This is the
+ * most common type of offers an agent should receive.
+ *
+ * @return true if the content of the offer is related to a user request.
+ */
+ abstract boolean isUserRequest();
+
+ /**
+ * Returns true if the content of the offer is related to a room invitation made by another
+ * agent. This type of offer include the room to join, metadata sent by the user while joining
+ * the queue and the reason why the agent is being invited.
+ *
+ * @return true if the content of the offer is related to a room invitation made by another agent.
+ */
+ abstract boolean isInvitation();
+
+ /**
+ * Returns true if the content of the offer is related to a service transfer made by another
+ * agent. This type of offers include the room to join, metadata sent by the user while joining the
+ * queue and the reason why the agent is receiving the transfer offer.
+ *
+ * @return true if the content of the offer is related to a service transfer made by another agent.
+ */
+ abstract boolean isTransfer();
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/OfferListener.java b/src/org/jivesoftware/smackx/workgroup/agent/OfferListener.java
new file mode 100644
index 0000000..5efde99
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/OfferListener.java
@@ -0,0 +1,49 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+/**
+ * An interface which all classes interested in hearing about chat offers associated to a particular
+ * AgentSession instance should implement.<br>
+ *
+ * @author Matt Tucker
+ * @author loki der quaeler
+ * @see org.jivesoftware.smackx.workgroup.agent.AgentSession
+ */
+public interface OfferListener {
+
+ /**
+ * The implementing class instance will be notified via this when the AgentSession has received
+ * an offer for a chat. The instance will then have the ability to accept, reject, or ignore
+ * the request (resulting in a revocation-by-timeout).
+ *
+ * @param request the Offer instance embodying the details of the offer
+ */
+ public void offerReceived (Offer request);
+
+ /**
+ * The implementing class instance will be notified via this when the AgentSessino has received
+ * a revocation of a previously extended offer.
+ *
+ * @param revokedOffer the RevokedOffer instance embodying the details of the revoked offer
+ */
+ public void offerRevoked (RevokedOffer revokedOffer);
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java b/src/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java
new file mode 100644
index 0000000..9fcff9a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java
@@ -0,0 +1,60 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import java.util.Date;
+import java.util.Set;
+
+import org.jivesoftware.smackx.workgroup.QueueUser;
+
+public interface QueueUsersListener {
+
+ /**
+ * The status of the queue was updated.
+ *
+ * @param queue the workgroup queue.
+ * @param status the status of queue.
+ */
+ public void statusUpdated(WorkgroupQueue queue, WorkgroupQueue.Status status);
+
+ /**
+ * The average wait time of the queue was updated.
+ *
+ * @param queue the workgroup queue.
+ * @param averageWaitTime the average wait time of the queue.
+ */
+ public void averageWaitTimeUpdated(WorkgroupQueue queue, int averageWaitTime);
+
+ /**
+ * The date of oldest entry waiting in the queue was updated.
+ *
+ * @param queue the workgroup queue.
+ * @param oldestEntry the date of the oldest entry waiting in the queue.
+ */
+ public void oldestEntryUpdated(WorkgroupQueue queue, Date oldestEntry);
+
+ /**
+ * The list of users waiting in the queue was updated.
+ *
+ * @param queue the workgroup queue.
+ * @param users the list of users waiting in the queue.
+ */
+ public void usersUpdated(WorkgroupQueue queue, Set<QueueUser> users);
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java b/src/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java
new file mode 100644
index 0000000..dab4d91
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java
@@ -0,0 +1,98 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import java.util.Date;
+
+/**
+ * An immutable simple class to embody the information concerning a revoked offer, this is namely
+ * the reason, the workgroup, the userJID, and the timestamp which the message was received.<br>
+ *
+ * @author loki der quaeler
+ */
+public class RevokedOffer {
+
+ private String userJID;
+ private String userID;
+ private String workgroupName;
+ private String sessionID;
+ private String reason;
+ private Date timestamp;
+
+ /**
+ *
+ * @param userJID the JID of the user for which this revocation was issued.
+ * @param userID the user ID of the user for which this revocation was issued.
+ * @param workgroupName the fully qualified name of the workgroup
+ * @param sessionID the session id attributed to this chain of packets
+ * @param reason the server issued message as to why this revocation was issued.
+ * @param timestamp the timestamp at which the revocation was issued
+ */
+ RevokedOffer(String userJID, String userID, String workgroupName, String sessionID,
+ String reason, Date timestamp) {
+ super();
+
+ this.userJID = userJID;
+ this.userID = userID;
+ this.workgroupName = workgroupName;
+ this.sessionID = sessionID;
+ this.reason = reason;
+ this.timestamp = timestamp;
+ }
+
+ public String getUserJID() {
+ return userJID;
+ }
+
+ /**
+ * @return the jid of the user for which this revocation was issued
+ */
+ public String getUserID() {
+ return this.userID;
+ }
+
+ /**
+ * @return the fully qualified name of the workgroup
+ */
+ public String getWorkgroupName() {
+ return this.workgroupName;
+ }
+
+ /**
+ * @return the session id which will associate all packets for the pending chat
+ */
+ public String getSessionID() {
+ return this.sessionID;
+ }
+
+ /**
+ * @return the server issued message as to why this revocation was issued
+ */
+ public String getReason() {
+ return this.reason;
+ }
+
+ /**
+ * @return the timestamp at which the revocation was issued
+ */
+ public Date getTimestamp() {
+ return this.timestamp;
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java b/src/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java
new file mode 100644
index 0000000..8a3801f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java
@@ -0,0 +1,100 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smackx.workgroup.packet.Transcript;
+import org.jivesoftware.smackx.workgroup.packet.Transcripts;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+
+/**
+ * A TranscriptManager helps to retrieve the full conversation transcript of a given session
+ * {@link #getTranscript(String, String)} or to retrieve a list with the summary of all the
+ * conversations that a user had {@link #getTranscripts(String, String)}.
+ *
+ * @author Gaston Dombiak
+ */
+public class TranscriptManager {
+ private Connection connection;
+
+ public TranscriptManager(Connection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Returns the full conversation transcript of a given session.
+ *
+ * @param sessionID the id of the session to get the full transcript.
+ * @param workgroupJID the JID of the workgroup that will process the request.
+ * @return the full conversation transcript of a given session.
+ * @throws XMPPException if an error occurs while getting the information.
+ */
+ public Transcript getTranscript(String workgroupJID, String sessionID) throws XMPPException {
+ Transcript request = new Transcript(sessionID);
+ request.setTo(workgroupJID);
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ // Send the request
+ connection.sendPacket(request);
+
+ Transcript response = (Transcript) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+
+ /**
+ * Returns the transcripts of a given user. The answer will contain the complete history of
+ * conversations that a user had.
+ *
+ * @param userID the id of the user to get his conversations.
+ * @param workgroupJID the JID of the workgroup that will process the request.
+ * @return the transcripts of a given user.
+ * @throws XMPPException if an error occurs while getting the information.
+ */
+ public Transcripts getTranscripts(String workgroupJID, String userID) throws XMPPException {
+ Transcripts request = new Transcripts(userID);
+ request.setTo(workgroupJID);
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ // Send the request
+ connection.sendPacket(request);
+
+ Transcripts response = (Transcripts) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java b/src/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java
new file mode 100644
index 0000000..8260cd6
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java
@@ -0,0 +1,111 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import org.jivesoftware.smackx.workgroup.packet.TranscriptSearch;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.ReportedData;
+
+/**
+ * A TranscriptSearchManager helps to retrieve the form to use for searching transcripts
+ * {@link #getSearchForm(String)} or to submit a search form and return the results of
+ * the search {@link #submitSearch(String, Form)}.
+ *
+ * @author Gaston Dombiak
+ */
+public class TranscriptSearchManager {
+ private Connection connection;
+
+ public TranscriptSearchManager(Connection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Returns the Form to use for searching transcripts. It is unlikely that the server
+ * will change the form (without a restart) so it is safe to keep the returned form
+ * for future submissions.
+ *
+ * @param serviceJID the address of the workgroup service.
+ * @return the Form to use for searching transcripts.
+ * @throws XMPPException if an error occurs while sending the request to the server.
+ */
+ public Form getSearchForm(String serviceJID) throws XMPPException {
+ TranscriptSearch search = new TranscriptSearch();
+ search.setType(IQ.Type.GET);
+ search.setTo(serviceJID);
+
+ PacketCollector collector = connection.createPacketCollector(
+ new PacketIDFilter(search.getPacketID()));
+ connection.sendPacket(search);
+
+ TranscriptSearch response = (TranscriptSearch) collector.nextResult(
+ SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return Form.getFormFrom(response);
+ }
+
+ /**
+ * Submits the completed form and returns the result of the transcript search. The result
+ * will include all the data returned from the server so be careful with the amount of
+ * data that the search may return.
+ *
+ * @param serviceJID the address of the workgroup service.
+ * @param completedForm the filled out search form.
+ * @return the result of the transcript search.
+ * @throws XMPPException if an error occurs while submiting the search to the server.
+ */
+ public ReportedData submitSearch(String serviceJID, Form completedForm) throws XMPPException {
+ TranscriptSearch search = new TranscriptSearch();
+ search.setType(IQ.Type.GET);
+ search.setTo(serviceJID);
+ search.addExtension(completedForm.getDataFormToSend());
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(search.getPacketID()));
+ connection.sendPacket(search);
+
+ TranscriptSearch response = (TranscriptSearch) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return ReportedData.getReportedDataFrom(response);
+ }
+}
+
+
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java b/src/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java
new file mode 100644
index 0000000..a3abbaa
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java
@@ -0,0 +1,62 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+/**
+ * Request sent by an agent to transfer a support session to another agent or user.
+ *
+ * @author Gaston Dombiak
+ */
+public class TransferRequest extends OfferContent {
+
+ private String inviter;
+ private String room;
+ private String reason;
+
+ public TransferRequest(String inviter, String room, String reason) {
+ this.inviter = inviter;
+ this.room = room;
+ this.reason = reason;
+ }
+
+ public String getInviter() {
+ return inviter;
+ }
+
+ public String getRoom() {
+ return room;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ boolean isUserRequest() {
+ return false;
+ }
+
+ boolean isInvitation() {
+ return false;
+ }
+
+ boolean isTransfer() {
+ return true;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/UserRequest.java b/src/org/jivesoftware/smackx/workgroup/agent/UserRequest.java
new file mode 100644
index 0000000..ccaaaf3
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/UserRequest.java
@@ -0,0 +1,47 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+/**
+ * Requests made by users to get support by some agent.
+ *
+ * @author Gaston Dombiak
+ */
+public class UserRequest extends OfferContent {
+ // TODO Do we want to use a singleton? Should we store the userID here?
+ private static UserRequest instance = new UserRequest();
+
+ public static OfferContent getInstance() {
+ return instance;
+ }
+
+ boolean isUserRequest() {
+ return true;
+ }
+
+ boolean isInvitation() {
+ return false;
+ }
+
+ boolean isTransfer() {
+ return false;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java b/src/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java
new file mode 100644
index 0000000..b43c826
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java
@@ -0,0 +1,224 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.workgroup.agent;
+
+import java.util.*;
+
+import org.jivesoftware.smackx.workgroup.QueueUser;
+
+/**
+ * A queue in a workgroup, which is a pool of agents that are routed a specific type of
+ * chat request.
+ */
+public class WorkgroupQueue {
+
+ private String name;
+ private Status status = Status.CLOSED;
+
+ private int averageWaitTime = -1;
+ private Date oldestEntry = null;
+ private Set<QueueUser> users = Collections.emptySet();
+
+ private int maxChats = 0;
+ private int currentChats = 0;
+
+ /**
+ * Creates a new workgroup queue instance.
+ *
+ * @param name the name of the queue.
+ */
+ WorkgroupQueue(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of the queue.
+ *
+ * @return the name of the queue.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the status of the queue.
+ *
+ * @return the status of the queue.
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ void setStatus(Status status) {
+ this.status = status;
+ }
+
+ /**
+ * Returns the number of users waiting in the queue waiting to be routed to
+ * an agent.
+ *
+ * @return the number of users waiting in the queue.
+ */
+ public int getUserCount() {
+ if (users == null) {
+ return 0;
+ }
+ return users.size();
+ }
+
+ /**
+ * Returns an Iterator for the users in the queue waiting to be routed to
+ * an agent (QueueUser instances).
+ *
+ * @return an Iterator for the users waiting in the queue.
+ */
+ public Iterator<QueueUser> getUsers() {
+ if (users == null) {
+ return new HashSet<QueueUser>().iterator();
+ }
+ return Collections.unmodifiableSet(users).iterator();
+ }
+
+ void setUsers(Set<QueueUser> users) {
+ this.users = users;
+ }
+
+ /**
+ * Returns the average amount of time users wait in the queue before being
+ * routed to an agent. If average wait time info isn't available, -1 will
+ * be returned.
+ *
+ * @return the average wait time
+ */
+ public int getAverageWaitTime() {
+ return averageWaitTime;
+ }
+
+ void setAverageWaitTime(int averageTime) {
+ this.averageWaitTime = averageTime;
+ }
+
+ /**
+ * Returns the date of the oldest request waiting in the queue. If there
+ * are no requests waiting to be routed, this method will return <tt>null</tt>.
+ *
+ * @return the date of the oldest request in the queue.
+ */
+ public Date getOldestEntry() {
+ return oldestEntry;
+ }
+
+ void setOldestEntry(Date oldestEntry) {
+ this.oldestEntry = oldestEntry;
+ }
+
+ /**
+ * Returns the maximum number of simultaneous chats the queue can handle.
+ *
+ * @return the max number of chats the queue can handle.
+ */
+ public int getMaxChats() {
+ return maxChats;
+ }
+
+ void setMaxChats(int maxChats) {
+ this.maxChats = maxChats;
+ }
+
+ /**
+ * Returns the current number of active chat sessions in the queue.
+ *
+ * @return the current number of active chat sessions in the queue.
+ */
+ public int getCurrentChats() {
+ return currentChats;
+ }
+
+ void setCurrentChats(int currentChats) {
+ this.currentChats = currentChats;
+ }
+
+ /**
+ * A class to represent the status of the workgroup. The possible values are:
+ *
+ * <ul>
+ * <li>WorkgroupQueue.Status.OPEN -- the queue is active and accepting new chat requests.
+ * <li>WorkgroupQueue.Status.ACTIVE -- the queue is active but NOT accepting new chat
+ * requests.
+ * <li>WorkgroupQueue.Status.CLOSED -- the queue is NOT active and NOT accepting new
+ * chat requests.
+ * </ul>
+ */
+ public static class Status {
+
+ /**
+ * The queue is active and accepting new chat requests.
+ */
+ public static final Status OPEN = new Status("open");
+
+ /**
+ * The queue is active but NOT accepting new chat requests. This state might
+ * occur when the workgroup has closed because regular support hours have closed,
+ * but there are still several requests left in the queue.
+ */
+ public static final Status ACTIVE = new Status("active");
+
+ /**
+ * The queue is NOT active and NOT accepting new chat requests.
+ */
+ public static final Status CLOSED = new Status("closed");
+
+ /**
+ * Converts a String into the corresponding status. Valid String values
+ * that can be converted to a status are: "open", "active", and "closed".
+ *
+ * @param type the String value to covert.
+ * @return the corresponding Type.
+ */
+ public static Status fromString(String type) {
+ if (type == null) {
+ return null;
+ }
+ type = type.toLowerCase();
+ if (OPEN.toString().equals(type)) {
+ return OPEN;
+ }
+ else if (ACTIVE.toString().equals(type)) {
+ return ACTIVE;
+ }
+ else if (CLOSED.toString().equals(type)) {
+ return CLOSED;
+ }
+ else {
+ return null;
+ }
+ }
+
+ private String value;
+
+ private Status(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+ }
+} \ No newline at end of file