aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/workgroup
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/workgroup')
-rw-r--r--src/org/jivesoftware/smackx/workgroup/MetaData.java68
-rw-r--r--src/org/jivesoftware/smackx/workgroup/QueueUser.java85
-rw-r--r--src/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java134
-rw-r--r--src/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java39
-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
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java82
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java155
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java93
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java116
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java68
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java143
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java198
-rw-r--r--src/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java155
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java132
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java266
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java163
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java129
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java75
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java49
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java113
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java173
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java211
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java112
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java199
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java160
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java122
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java177
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java177
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/SessionID.java77
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/Transcript.java98
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java66
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java87
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/Transcripts.java247
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java148
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/UserID.java77
-rw-r--r--src/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java86
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java56
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java179
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java114
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java155
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java112
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java103
-rw-r--r--src/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java125
-rw-r--r--src/org/jivesoftware/smackx/workgroup/user/QueueListener.java55
-rw-r--r--src/org/jivesoftware/smackx/workgroup/user/Workgroup.java868
-rw-r--r--src/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java132
-rw-r--r--src/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java103
-rw-r--r--src/org/jivesoftware/smackx/workgroup/util/ModelUtil.java322
64 files changed, 9785 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/workgroup/MetaData.java b/src/org/jivesoftware/smackx/workgroup/MetaData.java
new file mode 100644
index 0000000..115a79c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/MetaData.java
@@ -0,0 +1,68 @@
+/**
+ * $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;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smackx.workgroup.util.MetaDataUtils;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * MetaData packet extension.
+ */
+public class MetaData implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "metadata";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ private Map<String, List<String>> metaData;
+
+ public MetaData(Map<String, List<String>> metaData) {
+ this.metaData = metaData;
+ }
+
+ /**
+ * @return the Map of metadata contained by this instance
+ */
+ public Map<String, List<String>> getMetaData() {
+ return metaData;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ return MetaDataUtils.serializeMetaData(this.getMetaData());
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/QueueUser.java b/src/org/jivesoftware/smackx/workgroup/QueueUser.java
new file mode 100644
index 0000000..89a1899
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/QueueUser.java
@@ -0,0 +1,85 @@
+/**
+ * $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;
+
+import java.util.Date;
+
+/**
+ * An immutable class which wraps up customer-in-queue data return from the server; depending on
+ * the type of information dispatched from the server, not all information will be available in
+ * any given instance.
+ *
+ * @author loki der quaeler
+ */
+public class QueueUser {
+
+ private String userID;
+
+ private int queuePosition;
+ private int estimatedTime;
+ private Date joinDate;
+
+ /**
+ * @param uid the user jid of the customer in the queue
+ * @param position the position customer sits in the queue
+ * @param time the estimate of how much longer the customer will be in the queue in seconds
+ * @param joinedAt the timestamp of when the customer entered the queue
+ */
+ public QueueUser (String uid, int position, int time, Date joinedAt) {
+ super();
+
+ this.userID = uid;
+ this.queuePosition = position;
+ this.estimatedTime = time;
+ this.joinDate = joinedAt;
+ }
+
+ /**
+ * @return the user jid of the customer in the queue
+ */
+ public String getUserID () {
+ return this.userID;
+ }
+
+ /**
+ * @return the position in the queue at which the customer sits, or -1 if the update which
+ * this instance embodies is only a time update instead
+ */
+ public int getQueuePosition () {
+ return this.queuePosition;
+ }
+
+ /**
+ * @return the estimated time remaining of the customer in the queue in seconds, or -1 if
+ * if the update which this instance embodies is only a position update instead
+ */
+ public int getEstimatedRemainingTime () {
+ return this.estimatedTime;
+ }
+
+ /**
+ * @return the timestamp of when this customer entered the queue, or null if the server did not
+ * provide this information
+ */
+ public Date getQueueJoinTimestamp () {
+ return this.joinDate;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java b/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java
new file mode 100644
index 0000000..ac3b5b6
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java
@@ -0,0 +1,134 @@
+/**
+ * $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;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An immutable class wrapping up the basic information which comprises a group chat invitation.
+ *
+ * @author loki der quaeler
+ */
+public class WorkgroupInvitation {
+
+ protected String uniqueID;
+
+ protected String sessionID;
+
+ protected String groupChatName;
+ protected String issuingWorkgroupName;
+ protected String messageBody;
+ protected String invitationSender;
+ protected Map<String, List<String>> metaData;
+
+ /**
+ * This calls the 5-argument constructor with a null MetaData argument value
+ *
+ * @param jid the jid string with which the issuing AgentSession or Workgroup instance
+ * was created
+ * @param group the jid of the room to which the person is invited
+ * @param workgroup the jid of the workgroup issuing the invitation
+ * @param sessID the session id associated with the pending chat
+ * @param msgBody the body of the message which contained the invitation
+ * @param from the user jid who issued the invitation, if known, null otherwise
+ */
+ public WorkgroupInvitation (String jid, String group, String workgroup,
+ String sessID, String msgBody, String from) {
+ this(jid, group, workgroup, sessID, msgBody, from, null);
+ }
+
+ /**
+ * @param jid the jid string with which the issuing AgentSession or Workgroup instance
+ * was created
+ * @param group the jid of the room to which the person is invited
+ * @param workgroup the jid of the workgroup issuing the invitation
+ * @param sessID the session id associated with the pending chat
+ * @param msgBody the body of the message which contained the invitation
+ * @param from the user jid who issued the invitation, if known, null otherwise
+ * @param metaData the metadata sent with the invitation
+ */
+ public WorkgroupInvitation (String jid, String group, String workgroup, String sessID, String msgBody,
+ String from, Map<String, List<String>> metaData) {
+ super();
+
+ this.uniqueID = jid;
+ this.sessionID = sessID;
+ this.groupChatName = group;
+ this.issuingWorkgroupName = workgroup;
+ this.messageBody = msgBody;
+ this.invitationSender = from;
+ this.metaData = metaData;
+ }
+
+ /**
+ * @return the jid string with which the issuing AgentSession or Workgroup instance
+ * was created.
+ */
+ public String getUniqueID () {
+ return this.uniqueID;
+ }
+
+ /**
+ * @return the session id associated with the pending chat; working backwards temporally,
+ * this session id should match the session id to the corresponding offer request
+ * which resulted in this invitation.
+ */
+ public String getSessionID () {
+ return this.sessionID;
+ }
+
+ /**
+ * @return the jid of the room to which the person is invited.
+ */
+ public String getGroupChatName () {
+ return this.groupChatName;
+ }
+
+ /**
+ * @return the name of the workgroup from which the invitation was issued.
+ */
+ public String getWorkgroupName () {
+ return this.issuingWorkgroupName;
+ }
+
+ /**
+ * @return the contents of the body-block of the message that housed this invitation.
+ */
+ public String getMessageBody () {
+ return this.messageBody;
+ }
+
+ /**
+ * @return the user who issued the invitation, or null if it wasn't known.
+ */
+ public String getInvitationSender () {
+ return this.invitationSender;
+ }
+
+ /**
+ * @return the meta data associated with the invitation, or null if this instance was
+ * constructed with none
+ */
+ public Map<String, List<String>> getMetaData () {
+ return this.metaData;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java b/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java
new file mode 100644
index 0000000..bc73242
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java
@@ -0,0 +1,39 @@
+/**
+ * $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;
+
+/**
+ * An interface which all classes interested in hearing about group chat invitations should
+ * implement.
+ *
+ * @author loki der quaeler
+ */
+public interface WorkgroupInvitationListener {
+
+ /**
+ * The implementing class instance will be notified via this method when an invitation
+ * to join a group chat has been received from the server.
+ *
+ * @param invitation an Invitation instance embodying the information pertaining to the
+ * invitation
+ */
+ public void invitationReceived(WorkgroupInvitation invitation);
+
+}
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
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java b/src/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java
new file mode 100644
index 0000000..f2dc08e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java
@@ -0,0 +1,82 @@
+/**
+ * $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.ext.forms;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.v1.XmlPullParser;
+
+public class WorkgroupForm extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "workgroup-form";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for WebForm packets.
+ *
+ * @author Derek DeMoro
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public InternalProvider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ WorkgroupForm answer = new WorkgroupForm();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ // Parse the packet extension
+ answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(),
+ parser.getNamespace(), parser));
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return answer;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java b/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java
new file mode 100644
index 0000000..7b8d200
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java
@@ -0,0 +1,155 @@
+/**
+ * $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.ext.history;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * IQ provider used to retrieve individual agent information. Each chat session can be mapped
+ * to one or more jids and therefore retrievable.
+ */
+public class AgentChatHistory extends IQ {
+ private String agentJID;
+ private int maxSessions;
+ private long startDate;
+
+ private List<AgentChatSession> agentChatSessions = new ArrayList<AgentChatSession>();
+
+ public AgentChatHistory(String agentJID, int maxSessions, Date startDate) {
+ this.agentJID = agentJID;
+ this.maxSessions = maxSessions;
+ this.startDate = startDate.getTime();
+ }
+
+ public AgentChatHistory(String agentJID, int maxSessions) {
+ this.agentJID = agentJID;
+ this.maxSessions = maxSessions;
+ this.startDate = 0;
+ }
+
+ public AgentChatHistory() {
+ }
+
+ public void addChatSession(AgentChatSession chatSession) {
+ agentChatSessions.add(chatSession);
+ }
+
+ public Collection<AgentChatSession> getAgentChatSessions() {
+ return agentChatSessions;
+ }
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "chat-sessions";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append(" agentJID=\"" + agentJID + "\"");
+ buf.append(" maxSessions=\"" + maxSessions + "\"");
+ buf.append(" startDate=\"" + startDate + "\"");
+
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+ /**
+ * Packet extension provider for AgentHistory packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ AgentChatHistory agentChatHistory = new AgentChatHistory();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("chat-session".equals(parser.getName()))) {
+ agentChatHistory.addChatSession(parseChatSetting(parser));
+
+ }
+ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return agentChatHistory;
+ }
+
+ private AgentChatSession parseChatSetting(XmlPullParser parser) throws Exception {
+
+ boolean done = false;
+ Date date = null;
+ long duration = 0;
+ String visitorsName = null;
+ String visitorsEmail = null;
+ String sessionID = null;
+ String question = null;
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("date".equals(parser.getName()))) {
+ String dateStr = parser.nextText();
+ long l = Long.valueOf(dateStr).longValue();
+ date = new Date(l);
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("duration".equals(parser.getName()))) {
+ duration = Long.valueOf(parser.nextText()).longValue();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("visitorsName".equals(parser.getName()))) {
+ visitorsName = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("visitorsEmail".equals(parser.getName()))) {
+ visitorsEmail = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("sessionID".equals(parser.getName()))) {
+ sessionID = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("question".equals(parser.getName()))) {
+ question = parser.nextText();
+ }
+ else if (eventType == XmlPullParser.END_TAG && "chat-session".equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return new AgentChatSession(date, duration, visitorsName, visitorsEmail, sessionID, question);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java b/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java
new file mode 100644
index 0000000..5113cda
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java
@@ -0,0 +1,93 @@
+/**
+ * $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.ext.history;
+
+import java.util.Date;
+
+/**
+ * Represents one chat session for an agent.
+ */
+public class AgentChatSession {
+ public Date startDate;
+ public long duration;
+ public String visitorsName;
+ public String visitorsEmail;
+ public String sessionID;
+ public String question;
+
+ public AgentChatSession(Date date, long duration, String visitorsName, String visitorsEmail, String sessionID, String question) {
+ this.startDate = date;
+ this.duration = duration;
+ this.visitorsName = visitorsName;
+ this.visitorsEmail = visitorsEmail;
+ this.sessionID = sessionID;
+ this.question = question;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public long getDuration() {
+ return duration;
+ }
+
+ public void setDuration(long duration) {
+ this.duration = duration;
+ }
+
+ public String getVisitorsName() {
+ return visitorsName;
+ }
+
+ public void setVisitorsName(String visitorsName) {
+ this.visitorsName = visitorsName;
+ }
+
+ public String getVisitorsEmail() {
+ return visitorsEmail;
+ }
+
+ public void setVisitorsEmail(String visitorsEmail) {
+ this.visitorsEmail = visitorsEmail;
+ }
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public void setSessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ public void setQuestion(String question){
+ this.question = question;
+ }
+
+ public String getQuestion(){
+ return question;
+ }
+
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java b/src/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java
new file mode 100644
index 0000000..301e1a5
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java
@@ -0,0 +1,116 @@
+/**
+ * $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.ext.history;
+
+import org.jivesoftware.smackx.workgroup.util.MetaDataUtils;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ChatMetadata extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "chat-metadata";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+
+ private String sessionID;
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public void setSessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+
+ private Map<String, List<String>> map = new HashMap<String, List<String>>();
+
+ public void setMetadata(Map<String, List<String>> metadata){
+ this.map = metadata;
+ }
+
+ public Map<String, List<String>> getMetadata(){
+ return map;
+ }
+
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ buf.append("<sessionID>").append(getSessionID()).append("</sessionID>");
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for Metadata packets.
+ *
+ * @author Derek DeMoro
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ final ChatMetadata chatM = new ChatMetadata();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("sessionID")) {
+ chatM.setSessionID(parser.nextText());
+ }
+ else if (parser.getName().equals("metadata")) {
+ Map<String, List<String>> map = MetaDataUtils.parseMetaData(parser);
+ chatM.setMetadata(map);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return chatM;
+ }
+ }
+}
+
+
+
+
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java b/src/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java
new file mode 100644
index 0000000..acf6196
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java
@@ -0,0 +1,68 @@
+/**
+ * $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.ext.macros;
+
+/**
+ * Macro datamodel.
+ */
+public class Macro {
+ public static final int TEXT = 0;
+ public static final int URL = 1;
+ public static final int IMAGE = 2;
+
+
+ private String title;
+ private String description;
+ private String response;
+ private int type;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getResponse() {
+ return response;
+ }
+
+ public void setResponse(String response) {
+ this.response = response;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java b/src/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java
new file mode 100644
index 0000000..0742b3d
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java
@@ -0,0 +1,143 @@
+/**
+ * $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.ext.macros;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * MacroGroup datamodel.
+ */
+public class MacroGroup {
+ private List<Macro> macros;
+ private List<MacroGroup> macroGroups;
+
+
+ // Define MacroGroup
+ private String title;
+
+ public MacroGroup() {
+ macros = new ArrayList<Macro>();
+ macroGroups = new ArrayList<MacroGroup>();
+ }
+
+ public void addMacro(Macro macro) {
+ macros.add(macro);
+ }
+
+ public void removeMacro(Macro macro) {
+ macros.remove(macro);
+ }
+
+ public Macro getMacroByTitle(String title) {
+ Collection<Macro> col = Collections.unmodifiableList(macros);
+ Iterator<Macro> iter = col.iterator();
+ while (iter.hasNext()) {
+ Macro macro = (Macro)iter.next();
+ if (macro.getTitle().equalsIgnoreCase(title)) {
+ return macro;
+ }
+ }
+ return null;
+ }
+
+ public void addMacroGroup(MacroGroup group) {
+ macroGroups.add(group);
+ }
+
+ public void removeMacroGroup(MacroGroup group) {
+ macroGroups.remove(group);
+ }
+
+ public Macro getMacro(int location) {
+ return (Macro)macros.get(location);
+ }
+
+ public MacroGroup getMacroGroupByTitle(String title) {
+ Collection<MacroGroup> col = Collections.unmodifiableList(macroGroups);
+ Iterator<MacroGroup> iter = col.iterator();
+ while (iter.hasNext()) {
+ MacroGroup group = (MacroGroup)iter.next();
+ if (group.getTitle().equalsIgnoreCase(title)) {
+ return group;
+ }
+ }
+ return null;
+ }
+
+ public MacroGroup getMacroGroup(int location) {
+ return (MacroGroup)macroGroups.get(location);
+ }
+
+
+ public List<Macro> getMacros() {
+ return macros;
+ }
+
+ public void setMacros(List<Macro> macros) {
+ this.macros = macros;
+ }
+
+ public List<MacroGroup> getMacroGroups() {
+ return macroGroups;
+ }
+
+ public void setMacroGroups(List<MacroGroup> macroGroups) {
+ this.macroGroups = macroGroups;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<macrogroup>");
+ buf.append("<title>" + getTitle() + "</title>");
+ buf.append("<macros>");
+ for (Macro macro : getMacros())
+ {
+ buf.append("<macro>");
+ buf.append("<title>" + macro.getTitle() + "</title>");
+ buf.append("<type>" + macro.getType() + "</type>");
+ buf.append("<description>" + macro.getDescription() + "</description>");
+ buf.append("<response>" + macro.getResponse() + "</response>");
+ buf.append("</macro>");
+ }
+ buf.append("</macros>");
+
+ if (getMacroGroups().size() > 0) {
+ buf.append("<macroGroups>");
+ for (MacroGroup groups : getMacroGroups()) {
+ buf.append(groups.toXML());
+ }
+ buf.append("</macroGroups>");
+ }
+ buf.append("</macrogroup>");
+ return buf.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java b/src/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java
new file mode 100644
index 0000000..869ec57
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java
@@ -0,0 +1,198 @@
+/**
+ * $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.ext.macros;
+
+import java.io.StringReader;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.StringUtils;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Macros iq is responsible for handling global and personal macros in the a Live Assistant
+ * Workgroup.
+ */
+public class Macros extends IQ {
+
+ private MacroGroup rootGroup;
+ private boolean personal;
+ private MacroGroup personalMacroGroup;
+
+ public MacroGroup getRootGroup() {
+ return rootGroup;
+ }
+
+ public void setRootGroup(MacroGroup rootGroup) {
+ this.rootGroup = rootGroup;
+ }
+
+ public boolean isPersonal() {
+ return personal;
+ }
+
+ public void setPersonal(boolean personal) {
+ this.personal = personal;
+ }
+
+ public MacroGroup getPersonalMacroGroup() {
+ return personalMacroGroup;
+ }
+
+ public void setPersonalMacroGroup(MacroGroup personalMacroGroup) {
+ this.personalMacroGroup = personalMacroGroup;
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "macros";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ if (isPersonal()) {
+ buf.append("<personal>true</personal>");
+ }
+ if (getPersonalMacroGroup() != null) {
+ buf.append("<personalMacro>");
+ buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML()));
+ buf.append("</personalMacro>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for Macro packets.
+ *
+ * @author Derek DeMoro
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public InternalProvider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ Macros macroGroup = new Macros();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("model")) {
+ String macros = parser.nextText();
+ MacroGroup group = parseMacroGroups(macros);
+ macroGroup.setRootGroup(group);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return macroGroup;
+ }
+
+ public Macro parseMacro(XmlPullParser parser) throws Exception {
+ Macro macro = new Macro();
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("title")) {
+ parser.next();
+ macro.setTitle(parser.getText());
+ }
+ else if (parser.getName().equals("description")) {
+ macro.setDescription(parser.nextText());
+ }
+ else if (parser.getName().equals("response")) {
+ macro.setResponse(parser.nextText());
+ }
+ else if (parser.getName().equals("type")) {
+ macro.setType(Integer.valueOf(parser.nextText()).intValue());
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("macro")) {
+ done = true;
+ }
+ }
+ }
+ return macro;
+ }
+
+ public MacroGroup parseMacroGroup(XmlPullParser parser) throws Exception {
+ MacroGroup group = new MacroGroup();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("macrogroup")) {
+ group.addMacroGroup(parseMacroGroup(parser));
+ }
+ if (parser.getName().equals("title")) {
+ group.setTitle(parser.nextText());
+ }
+ if (parser.getName().equals("macro")) {
+ group.addMacro(parseMacro(parser));
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("macrogroup")) {
+ done = true;
+ }
+ }
+ }
+ return group;
+ }
+
+ public MacroGroup parseMacroGroups(String macros) throws Exception {
+
+ MacroGroup group = null;
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(new StringReader(macros));
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("macrogroup")) {
+ group = parseMacroGroup(parser);
+ }
+ }
+ }
+ return group;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java b/src/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java
new file mode 100644
index 0000000..eff3c6c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java
@@ -0,0 +1,155 @@
+/**
+ * $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.ext.notes;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * IQ packet for retrieving and adding Chat Notes.
+ */
+public class ChatNotes extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "chat-notes";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+
+ private String sessionID;
+ private String notes;
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public void setSessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ buf.append("<sessionID>").append(getSessionID()).append("</sessionID>");
+
+ if (getNotes() != null) {
+ buf.append("<notes>").append(getNotes()).append("</notes>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for ChatNotes packets.
+ *
+ * @author Derek DeMoro
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ ChatNotes chatNotes = new ChatNotes();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("sessionID")) {
+ chatNotes.setSessionID(parser.nextText());
+ }
+ else if (parser.getName().equals("text")) {
+ String note = parser.nextText();
+ note = note.replaceAll("\\\\n", "\n");
+ chatNotes.setNotes(note);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return chatNotes;
+ }
+ }
+
+ /**
+ * Replaces all instances of oldString with newString in string.
+ *
+ * @param string the String to search to perform replacements on
+ * @param oldString the String that should be replaced by newString
+ * @param newString the String that will replace all instances of oldString
+ * @return a String will all instances of oldString replaced by newString
+ */
+ public static final String replace(String string, String oldString, String newString) {
+ if (string == null) {
+ return null;
+ }
+ // If the newString is null or zero length, just return the string since there's nothing
+ // to replace.
+ if (newString == null) {
+ return string;
+ }
+ int i = 0;
+ // Make sure that oldString appears at least once before doing any processing.
+ if ((i = string.indexOf(oldString, i)) >= 0) {
+ // Use char []'s, as they are more efficient to deal with.
+ char[] string2 = string.toCharArray();
+ char[] newString2 = newString.toCharArray();
+ int oLength = oldString.length();
+ StringBuilder buf = new StringBuilder(string2.length);
+ buf.append(string2, 0, i).append(newString2);
+ i += oLength;
+ int j = i;
+ // Replace all remaining instances of oldString with newString.
+ while ((i = string.indexOf(oldString, i)) > 0) {
+ buf.append(string2, j, i - j).append(newString2);
+ i += oLength;
+ j = i;
+ }
+ buf.append(string2, j, string2.length - j);
+ return buf.toString();
+ }
+ return string;
+ }
+}
+
+
+
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java b/src/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java
new file mode 100644
index 0000000..8b9d230
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java
@@ -0,0 +1,132 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * IQ packet for retrieving and changing the Agent personal information.
+ */
+public class AgentInfo extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "agent-info";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ private String jid;
+ private String name;
+
+ /**
+ * Returns the Agent's jid.
+ *
+ * @return the Agent's jid.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Sets the Agent's jid.
+ *
+ * @param jid the jid of the agent.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Returns the Agent's name. The name of the agent may be different than the user's name.
+ * This property may be shown in the webchat client.
+ *
+ * @return the Agent's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the Agent's name. The name of the agent may be different than the user's name.
+ * This property may be shown in the webchat client.
+ *
+ * @param name the new name of the agent.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ if (jid != null) {
+ buf.append("<jid>").append(getJid()).append("</jid>");
+ }
+ if (name != null) {
+ buf.append("<name>").append(getName()).append("</name>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for AgentInfo packets.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ AgentInfo answer = new AgentInfo();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("jid")) {
+ answer.setJid(parser.nextText());
+ }
+ else if (parser.getName().equals("name")) {
+ answer.setName(parser.nextText());
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return answer;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java b/src/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java
new file mode 100644
index 0000000..9f49033
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java
@@ -0,0 +1,266 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Agent status packet.
+ *
+ * @author Matt Tucker
+ */
+public class AgentStatus implements PacketExtension {
+
+ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+
+ static {
+ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
+ }
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "agent-status";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private String workgroupJID;
+ private List<ChatInfo> currentChats = new ArrayList<ChatInfo>();
+ private int maxChats = -1;
+
+ AgentStatus() {
+ }
+
+ public String getWorkgroupJID() {
+ return workgroupJID;
+ }
+
+ /**
+ * Returns a collection of ChatInfo where each ChatInfo represents a Chat where this agent
+ * is participating.
+ *
+ * @return a collection of ChatInfo where each ChatInfo represents a Chat where this agent
+ * is participating.
+ */
+ public List<ChatInfo> getCurrentChats() {
+ return Collections.unmodifiableList(currentChats);
+ }
+
+ public int getMaxChats() {
+ return maxChats;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\"");
+ if (workgroupJID != null) {
+ buf.append(" jid=\"").append(workgroupJID).append("\"");
+ }
+ buf.append(">");
+ if (maxChats != -1) {
+ buf.append("<max-chats>").append(maxChats).append("</max-chats>");
+ }
+ if (!currentChats.isEmpty()) {
+ buf.append("<current-chats xmlns= \"http://jivesoftware.com/protocol/workgroup\">");
+ for (Iterator<ChatInfo> it = currentChats.iterator(); it.hasNext();) {
+ buf.append(((ChatInfo)it.next()).toXML());
+ }
+ buf.append("</current-chats>");
+ }
+ buf.append("</").append(this.getElementName()).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * Represents information about a Chat where this Agent is participating.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class ChatInfo {
+
+ private String sessionID;
+ private String userID;
+ private Date date;
+ private String email;
+ private String username;
+ private String question;
+
+ public ChatInfo(String sessionID, String userID, Date date, String email, String username, String question) {
+ this.sessionID = sessionID;
+ this.userID = userID;
+ this.date = date;
+ this.email = email;
+ this.username = username;
+ this.question = question;
+ }
+
+ /**
+ * Returns the sessionID associated to this chat. Each chat will have a unique sessionID
+ * that could be used for retrieving the whole transcript of the conversation.
+ *
+ * @return the sessionID associated to this chat.
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the user unique identification of the user that made the initial request and
+ * for which this chat was generated. If the user joined using an anonymous connection
+ * then the userID will be the value of the ID attribute of the USER element. Otherwise,
+ * the userID will be the bare JID of the user that made the request.
+ *
+ * @return the user unique identification of the user that made the initial request.
+ */
+ public String getUserID() {
+ return userID;
+ }
+
+ /**
+ * Returns the date when this agent joined the chat.
+ *
+ * @return the date when this agent joined the chat.
+ */
+ public Date getDate() {
+ return date;
+ }
+
+ /**
+ * Returns the email address associated with the user.
+ *
+ * @return the email address associated with the user.
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * Returns the username(nickname) associated with the user.
+ *
+ * @return the username associated with the user.
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Returns the question the user asked.
+ *
+ * @return the question the user asked, if any.
+ */
+ public String getQuestion() {
+ return question;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<chat ");
+ if (sessionID != null) {
+ buf.append(" sessionID=\"").append(sessionID).append("\"");
+ }
+ if (userID != null) {
+ buf.append(" userID=\"").append(userID).append("\"");
+ }
+ if (date != null) {
+ buf.append(" startTime=\"").append(UTC_FORMAT.format(date)).append("\"");
+ }
+ if (email != null) {
+ buf.append(" email=\"").append(email).append("\"");
+ }
+ if (username != null) {
+ buf.append(" username=\"").append(username).append("\"");
+ }
+ if (question != null) {
+ buf.append(" question=\"").append(question).append("\"");
+ }
+ buf.append("/>");
+
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Packet extension provider for AgentStatus packets.
+ */
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ AgentStatus agentStatus = new AgentStatus();
+
+ agentStatus.workgroupJID = parser.getAttributeValue("", "jid");
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+
+ if (eventType == XmlPullParser.START_TAG) {
+ if ("chat".equals(parser.getName())) {
+ agentStatus.currentChats.add(parseChatInfo(parser));
+ }
+ else if ("max-chats".equals(parser.getName())) {
+ agentStatus.maxChats = Integer.parseInt(parser.nextText());
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG &&
+ ELEMENT_NAME.equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return agentStatus;
+ }
+
+ private ChatInfo parseChatInfo(XmlPullParser parser) {
+
+ String sessionID = parser.getAttributeValue("", "sessionID");
+ String userID = parser.getAttributeValue("", "userID");
+ Date date = null;
+ try {
+ date = UTC_FORMAT.parse(parser.getAttributeValue("", "startTime"));
+ }
+ catch (ParseException e) {
+ }
+
+ String email = parser.getAttributeValue("", "email");
+ String username = parser.getAttributeValue("", "username");
+ String question = parser.getAttributeValue("", "question");
+
+ return new ChatInfo(sessionID, userID, date, email, username, question);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java b/src/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java
new file mode 100644
index 0000000..48549d2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java
@@ -0,0 +1,163 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Agent status request packet. This packet is used by agents to request the list of
+ * agents in a workgroup. The response packet contains a list of packets. Presence
+ * packets from individual agents follow.
+ *
+ * @author Matt Tucker
+ */
+public class AgentStatusRequest extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "agent-status-request";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private Set<Item> agents;
+
+ public AgentStatusRequest() {
+ agents = new HashSet<Item>();
+ }
+
+ public int getAgentCount() {
+ return agents.size();
+ }
+
+ public Set<Item> getAgents() {
+ return Collections.unmodifiableSet(agents);
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ synchronized (agents) {
+ for (Iterator<Item> i=agents.iterator(); i.hasNext(); ) {
+ Item item = (Item) i.next();
+ buf.append("<agent jid=\"").append(item.getJID()).append("\">");
+ if (item.getName() != null) {
+ buf.append("<name xmlns=\""+ AgentInfo.NAMESPACE + "\">");
+ buf.append(item.getName());
+ buf.append("</name>");
+ }
+ buf.append("</agent>");
+ }
+ }
+ buf.append("</").append(this.getElementName()).append("> ");
+ return buf.toString();
+ }
+
+ public static class Item {
+
+ private String jid;
+ private String type;
+ private String name;
+
+ public Item(String jid, String type, String name) {
+ this.jid = jid;
+ this.type = type;
+ this.name = name;
+ }
+
+ public String getJID() {
+ return jid;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ /**
+ * Packet extension provider for AgentStatusRequest packets.
+ */
+ public static class Provider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ AgentStatusRequest statusRequest = new AgentStatusRequest();
+
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("agent".equals(parser.getName()))) {
+ statusRequest.agents.add(parseAgent(parser));
+ }
+ else if (eventType == XmlPullParser.END_TAG &&
+ "agent-status-request".equals(parser.getName()))
+ {
+ done = true;
+ }
+ }
+ return statusRequest;
+ }
+
+ private Item parseAgent(XmlPullParser parser) throws Exception {
+
+ boolean done = false;
+ String jid = parser.getAttributeValue("", "jid");
+ String type = parser.getAttributeValue("", "type");
+ String name = null;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) {
+ name = parser.nextText();
+ }
+ else if (eventType == XmlPullParser.END_TAG &&
+ "agent".equals(parser.getName()))
+ {
+ done = true;
+ }
+ }
+ return new Item(jid, type, name);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java b/src/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java
new file mode 100644
index 0000000..292a640
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java
@@ -0,0 +1,129 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a request for getting the jid of the workgroups where an agent can work or could
+ * represent the result of such request which will contain the list of workgroups JIDs where the
+ * agent can work.
+ *
+ * @author Gaston Dombiak
+ */
+public class AgentWorkgroups extends IQ {
+
+ private String agentJID;
+ private List<String> workgroups;
+
+ /**
+ * Creates an AgentWorkgroups request for the given agent. This IQ will be sent and an answer
+ * will be received with the jid of the workgroups where the agent can work.
+ *
+ * @param agentJID the id of the agent to get his workgroups.
+ */
+ public AgentWorkgroups(String agentJID) {
+ this.agentJID = agentJID;
+ this.workgroups = new ArrayList<String>();
+ }
+
+ /**
+ * Creates an AgentWorkgroups which will contain the JIDs of the workgroups where an agent can
+ * work.
+ *
+ * @param agentJID the id of the agent that can work in the list of workgroups.
+ * @param workgroups the list of workgroup JIDs where the agent can work.
+ */
+ public AgentWorkgroups(String agentJID, List<String> workgroups) {
+ this.agentJID = agentJID;
+ this.workgroups = workgroups;
+ }
+
+ public String getAgentJID() {
+ return agentJID;
+ }
+
+ /**
+ * Returns a list of workgroup JIDs where the agent can work.
+ *
+ * @return a list of workgroup JIDs where the agent can work.
+ */
+ public List<String> getWorkgroups() {
+ return Collections.unmodifiableList(workgroups);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<workgroups xmlns=\"http://jabber.org/protocol/workgroup\" jid=\"")
+ .append(agentJID)
+ .append("\">");
+
+ for (Iterator<String> it=workgroups.iterator(); it.hasNext();) {
+ String workgroupJID = it.next();
+ buf.append("<workgroup jid=\"" + workgroupJID + "\"/>");
+ }
+
+ buf.append("</workgroups>");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for AgentWorkgroups packets.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ String agentJID = parser.getAttributeValue("", "jid");
+ List<String> workgroups = new ArrayList<String>();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("workgroup")) {
+ workgroups.add(parser.getAttributeValue("", "jid"));
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("workgroups")) {
+ done = true;
+ }
+ }
+ }
+
+ return new AgentWorkgroups(agentJID, workgroups);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java b/src/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java
new file mode 100644
index 0000000..620291c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java
@@ -0,0 +1,75 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * A IQ packet used to depart a workgroup queue. There are two cases for issuing a depart
+ * queue request:<ul>
+ * <li>The user wants to leave the queue. In this case, an instance of this class
+ * should be created without passing in a user address.
+ * <li>An administrator or the server removes wants to remove a user from the queue.
+ * In that case, the address of the user to remove from the queue should be
+ * used to create an instance of this class.</ul>
+ *
+ * @author loki der quaeler
+ */
+public class DepartQueuePacket extends IQ {
+
+ private String user;
+
+ /**
+ * Creates a depart queue request packet to the specified workgroup.
+ *
+ * @param workgroup the workgroup to depart.
+ */
+ public DepartQueuePacket(String workgroup) {
+ this(workgroup, null);
+ }
+
+ /**
+ * Creates a depart queue request to the specified workgroup and for the
+ * specified user.
+ *
+ * @param workgroup the workgroup to depart.
+ * @param user the user to make depart from the queue.
+ */
+ public DepartQueuePacket(String workgroup, String user) {
+ this.user = user;
+
+ setTo(workgroup);
+ setType(IQ.Type.SET);
+ setFrom(user);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder("<depart-queue xmlns=\"http://jabber.org/protocol/workgroup\"");
+
+ if (this.user != null) {
+ buf.append("><jid>").append(this.user).append("</jid></depart-queue>");
+ }
+ else {
+ buf.append("/>");
+ }
+
+ return buf.toString();
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java b/src/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java
new file mode 100644
index 0000000..af76986
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.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.packet;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smackx.workgroup.MetaData;
+import org.jivesoftware.smackx.workgroup.util.MetaDataUtils;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * This provider parses meta data if it's not contained already in a larger extension provider.
+ *
+ * @author loki der quaeler
+ */
+public class MetaDataProvider implements PacketExtensionProvider {
+
+ /**
+ * PacketExtensionProvider implementation
+ */
+ public PacketExtension parseExtension (XmlPullParser parser)
+ throws Exception {
+ Map<String, List<String>> metaData = MetaDataUtils.parseMetaData(parser);
+
+ return new MetaData(metaData);
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java b/src/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java
new file mode 100644
index 0000000..0ceecae
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java
@@ -0,0 +1,113 @@
+/**
+ * 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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class MonitorPacket extends IQ {
+
+ private String sessionID;
+
+ private boolean isMonitor;
+
+ public boolean isMonitor() {
+ return isMonitor;
+ }
+
+ public void setMonitor(boolean monitor) {
+ isMonitor = monitor;
+ }
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public void setSessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "monitor";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append(">");
+ if (sessionID != null) {
+ buf.append("<makeOwner sessionID=\""+sessionID+"\"></makeOwner>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+
+ /**
+ * Packet extension provider for Monitor Packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ MonitorPacket packet = new MonitorPacket();
+
+ boolean done = false;
+
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("isMonitor".equals(parser.getName()))) {
+ String value = parser.nextText();
+ if ("false".equalsIgnoreCase(value)) {
+ packet.setMonitor(false);
+ }
+ else {
+ packet.setMonitor(true);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG && "monitor".equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ return packet;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java b/src/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java
new file mode 100644
index 0000000..0f80866
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java
@@ -0,0 +1,173 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Packet used for requesting information about occupants of a room or for retrieving information
+ * such information.
+ *
+ * @author Gaston Dombiak
+ */
+public class OccupantsInfo extends IQ {
+
+ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+
+ static {
+ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
+ }
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "occupants-info";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ private String roomID;
+ private final Set<OccupantInfo> occupants;
+
+ public OccupantsInfo(String roomID) {
+ this.roomID = roomID;
+ this.occupants = new HashSet<OccupantInfo>();
+ }
+
+ public String getRoomID() {
+ return roomID;
+ }
+
+ public int getOccupantsCount() {
+ return occupants.size();
+ }
+
+ public Set<OccupantInfo> getOccupants() {
+ return Collections.unmodifiableSet(occupants);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE);
+ buf.append("\" roomID=\"").append(roomID).append("\">");
+ synchronized (occupants) {
+ for (OccupantInfo occupant : occupants) {
+ buf.append("<occupant>");
+ // Add the occupant jid
+ buf.append("<jid>");
+ buf.append(occupant.getJID());
+ buf.append("</jid>");
+ // Add the occupant nickname
+ buf.append("<name>");
+ buf.append(occupant.getNickname());
+ buf.append("</name>");
+ // Add the date when the occupant joined the room
+ buf.append("<joined>");
+ buf.append(UTC_FORMAT.format(occupant.getJoined()));
+ buf.append("</joined>");
+ buf.append("</occupant>");
+ }
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+ public static class OccupantInfo {
+
+ private String jid;
+ private String nickname;
+ private Date joined;
+
+ public OccupantInfo(String jid, String nickname, Date joined) {
+ this.jid = jid;
+ this.nickname = nickname;
+ this.joined = joined;
+ }
+
+ public String getJID() {
+ return jid;
+ }
+
+ public String getNickname() {
+ return nickname;
+ }
+
+ public Date getJoined() {
+ return joined;
+ }
+ }
+
+ /**
+ * Packet extension provider for AgentStatusRequest packets.
+ */
+ public static class Provider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+ OccupantsInfo occupantsInfo = new OccupantsInfo(parser.getAttributeValue("", "roomID"));
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) &&
+ ("occupant".equals(parser.getName()))) {
+ occupantsInfo.occupants.add(parseOccupantInfo(parser));
+ } else if (eventType == XmlPullParser.END_TAG &&
+ ELEMENT_NAME.equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return occupantsInfo;
+ }
+
+ private OccupantInfo parseOccupantInfo(XmlPullParser parser) throws Exception {
+
+ boolean done = false;
+ String jid = null;
+ String nickname = null;
+ Date joined = null;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("jid".equals(parser.getName()))) {
+ jid = parser.nextText();
+ } else if ((eventType == XmlPullParser.START_TAG) &&
+ ("nickname".equals(parser.getName()))) {
+ nickname = parser.nextText();
+ } else if ((eventType == XmlPullParser.START_TAG) &&
+ ("joined".equals(parser.getName()))) {
+ joined = UTC_FORMAT.parse(parser.nextText());
+ } else if (eventType == XmlPullParser.END_TAG &&
+ "occupant".equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return new OccupantInfo(jid, nickname, joined);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java b/src/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java
new file mode 100644
index 0000000..8f56b78
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java
@@ -0,0 +1,211 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smackx.workgroup.MetaData;
+import org.jivesoftware.smackx.workgroup.agent.InvitationRequest;
+import org.jivesoftware.smackx.workgroup.agent.OfferContent;
+import org.jivesoftware.smackx.workgroup.agent.TransferRequest;
+import org.jivesoftware.smackx.workgroup.agent.UserRequest;
+import org.jivesoftware.smackx.workgroup.util.MetaDataUtils;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An IQProvider for agent offer requests.
+ *
+ * @author loki der quaeler
+ */
+public class OfferRequestProvider implements IQProvider {
+
+ public OfferRequestProvider() {
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ int eventType = parser.getEventType();
+ String sessionID = null;
+ int timeout = -1;
+ OfferContent content = null;
+ boolean done = false;
+ Map<String, List<String>> metaData = new HashMap<String, List<String>>();
+
+ if (eventType != XmlPullParser.START_TAG) {
+ // throw exception
+ }
+
+ String userJID = parser.getAttributeValue("", "jid");
+ // Default userID to the JID.
+ String userID = userJID;
+
+ while (!done) {
+ eventType = parser.next();
+
+ if (eventType == XmlPullParser.START_TAG) {
+ String elemName = parser.getName();
+
+ if ("timeout".equals(elemName)) {
+ timeout = Integer.parseInt(parser.nextText());
+ }
+ else if (MetaData.ELEMENT_NAME.equals(elemName)) {
+ metaData = MetaDataUtils.parseMetaData(parser);
+ }
+ else if (SessionID.ELEMENT_NAME.equals(elemName)) {
+ sessionID = parser.getAttributeValue("", "id");
+ }
+ else if (UserID.ELEMENT_NAME.equals(elemName)) {
+ userID = parser.getAttributeValue("", "id");
+ }
+ else if ("user-request".equals(elemName)) {
+ content = UserRequest.getInstance();
+ }
+ else if (RoomInvitation.ELEMENT_NAME.equals(elemName)) {
+ RoomInvitation invitation = (RoomInvitation) PacketParserUtils
+ .parsePacketExtension(RoomInvitation.ELEMENT_NAME, RoomInvitation.NAMESPACE, parser);
+ content = new InvitationRequest(invitation.getInviter(), invitation.getRoom(),
+ invitation.getReason());
+ }
+ else if (RoomTransfer.ELEMENT_NAME.equals(elemName)) {
+ RoomTransfer transfer = (RoomTransfer) PacketParserUtils
+ .parsePacketExtension(RoomTransfer.ELEMENT_NAME, RoomTransfer.NAMESPACE, parser);
+ content = new TransferRequest(transfer.getInviter(), transfer.getRoom(), transfer.getReason());
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if ("offer".equals(parser.getName())) {
+ done = true;
+ }
+ }
+ }
+
+ OfferRequestPacket offerRequest =
+ new OfferRequestPacket(userJID, userID, timeout, metaData, sessionID, content);
+ offerRequest.setType(IQ.Type.SET);
+
+ return offerRequest;
+ }
+
+ public static class OfferRequestPacket extends IQ {
+
+ private int timeout;
+ private String userID;
+ private String userJID;
+ private Map<String, List<String>> metaData;
+ private String sessionID;
+ private OfferContent content;
+
+ public OfferRequestPacket(String userJID, String userID, int timeout, Map<String, List<String>> metaData,
+ String sessionID, OfferContent content)
+ {
+ this.userJID = userJID;
+ this.userID = userID;
+ this.timeout = timeout;
+ this.metaData = metaData;
+ this.sessionID = sessionID;
+ this.content = content;
+ }
+
+ /**
+ * Returns the userID, which is either the same as the userJID or a special
+ * value that the user provided as part of their "join queue" request.
+ *
+ * @return the user ID.
+ */
+ public String getUserID() {
+ return userID;
+ }
+
+ /**
+ * The JID of the user that made the "join queue" request.
+ *
+ * @return the user JID.
+ */
+ public String getUserJID() {
+ return userJID;
+ }
+
+ /**
+ * Returns the session ID associated with the request and ensuing chat. If the offer
+ * does not contain a session ID, <tt>null</tt> will be returned.
+ *
+ * @return the session id associated with the request.
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the number of seconds the agent has to accept the offer before
+ * it times out.
+ *
+ * @return the offer timeout (in seconds).
+ */
+ public int getTimeout() {
+ return this.timeout;
+ }
+
+ public OfferContent getContent() {
+ return content;
+ }
+
+ /**
+ * Returns any meta-data associated with the offer.
+ *
+ * @return meta-data associated with the offer.
+ */
+ public Map<String, List<String>> getMetaData() {
+ return this.metaData;
+ }
+
+ public String getChildElementXML () {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<offer xmlns=\"http://jabber.org/protocol/workgroup\" jid=\"").append(userJID).append("\">");
+ buf.append("<timeout>").append(timeout).append("</timeout>");
+
+ if (sessionID != null) {
+ buf.append('<').append(SessionID.ELEMENT_NAME);
+ buf.append(" session=\"");
+ buf.append(getSessionID()).append("\" xmlns=\"");
+ buf.append(SessionID.NAMESPACE).append("\"/>");
+ }
+
+ if (metaData != null) {
+ buf.append(MetaDataUtils.serializeMetaData(metaData));
+ }
+
+ if (userID != null) {
+ buf.append('<').append(UserID.ELEMENT_NAME);
+ buf.append(" id=\"");
+ buf.append(userID).append("\" xmlns=\"");
+ buf.append(UserID.NAMESPACE).append("\"/>");
+ }
+
+ buf.append("</offer>");
+
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java b/src/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java
new file mode 100644
index 0000000..202824c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java
@@ -0,0 +1,112 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * An IQProvider class which has savvy about the offer-revoke tag.<br>
+ *
+ * @author loki der quaeler
+ */
+public class OfferRevokeProvider implements IQProvider {
+
+ public IQ parseIQ (XmlPullParser parser) throws Exception {
+ // The parser will be positioned on the opening IQ tag, so get the JID attribute.
+ String userJID = parser.getAttributeValue("", "jid");
+ // Default the userID to the JID.
+ String userID = userJID;
+ String reason = null;
+ String sessionID = null;
+ boolean done = false;
+
+ while (!done) {
+ int eventType = parser.next();
+
+ if ((eventType == XmlPullParser.START_TAG) && parser.getName().equals("reason")) {
+ reason = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG)
+ && parser.getName().equals(SessionID.ELEMENT_NAME)) {
+ sessionID = parser.getAttributeValue("", "id");
+ }
+ else if ((eventType == XmlPullParser.START_TAG)
+ && parser.getName().equals(UserID.ELEMENT_NAME)) {
+ userID = parser.getAttributeValue("", "id");
+ }
+ else if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals(
+ "offer-revoke"))
+ {
+ done = true;
+ }
+ }
+
+ return new OfferRevokePacket(userJID, userID, reason, sessionID);
+ }
+
+ public class OfferRevokePacket extends IQ {
+
+ private String userJID;
+ private String userID;
+ private String sessionID;
+ private String reason;
+
+ public OfferRevokePacket (String userJID, String userID, String cause, String sessionID) {
+ this.userJID = userJID;
+ this.userID = userID;
+ this.reason = cause;
+ this.sessionID = sessionID;
+ }
+
+ public String getUserJID() {
+ return userJID;
+ }
+
+ public String getUserID() {
+ return this.userID;
+ }
+
+ public String getReason() {
+ return this.reason;
+ }
+
+ public String getSessionID() {
+ return this.sessionID;
+ }
+
+ public String getChildElementXML () {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<offer-revoke xmlns=\"http://jabber.org/protocol/workgroup\" jid=\"").append(userID).append("\">");
+ if (reason != null) {
+ buf.append("<reason>").append(reason).append("</reason>");
+ }
+ if (sessionID != null) {
+ buf.append(new SessionID(sessionID).toXML());
+ }
+ if (userID != null) {
+ buf.append(new UserID(userID).toXML());
+ }
+ buf.append("</offer-revoke>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/src/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java
new file mode 100644
index 0000000..ef11a78
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java
@@ -0,0 +1,199 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smackx.workgroup.QueueUser;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Queue details packet extension, which contains details about the users
+ * currently in a queue.
+ */
+public class QueueDetails implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "notify-queue-details";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss";
+
+ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+ /**
+ * The list of users in the queue.
+ */
+ private Set<QueueUser> users;
+
+ /**
+ * Creates a new QueueDetails packet
+ */
+ private QueueDetails() {
+ users = new HashSet<QueueUser>();
+ }
+
+ /**
+ * Returns the number of users currently in the queue that are waiting to
+ * be routed to an agent.
+ *
+ * @return the number of users in the queue.
+ */
+ public int getUserCount() {
+ return users.size();
+ }
+
+ /**
+ * Returns the set of users in the queue that are waiting to
+ * be routed to an agent (as QueueUser objects).
+ *
+ * @return a Set for the users waiting in a queue.
+ */
+ public Set<QueueUser> getUsers() {
+ synchronized (users) {
+ return users;
+ }
+ }
+
+ /**
+ * Adds a user to the packet.
+ *
+ * @param user the user.
+ */
+ private void addUser(QueueUser user) {
+ synchronized (users) {
+ users.add(user);
+ }
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+
+ synchronized (users) {
+ for (Iterator<QueueUser> i=users.iterator(); i.hasNext(); ) {
+ QueueUser user = (QueueUser)i.next();
+ int position = user.getQueuePosition();
+ int timeRemaining = user.getEstimatedRemainingTime();
+ Date timestamp = user.getQueueJoinTimestamp();
+
+ buf.append("<user jid=\"").append(user.getUserID()).append("\">");
+
+ if (position != -1) {
+ buf.append("<position>").append(position).append("</position>");
+ }
+
+ if (timeRemaining != -1) {
+ buf.append("<time>").append(timeRemaining).append("</time>");
+ }
+
+ if (timestamp != null) {
+ buf.append("<join-time>");
+ buf.append(dateFormat.format(timestamp));
+ buf.append("</join-time>");
+ }
+
+ buf.append("</user>");
+ }
+ }
+ buf.append("</").append(ELEMENT_NAME).append(">");
+ return buf.toString();
+ }
+
+ /**
+ * Provider class for QueueDetails packet extensions.
+ */
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+ QueueDetails queueDetails = new QueueDetails();
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_TAG &&
+ "notify-queue-details".equals(parser.getName()))
+ {
+ eventType = parser.next();
+ while ((eventType == XmlPullParser.START_TAG) && "user".equals(parser.getName())) {
+ String uid = null;
+ int position = -1;
+ int time = -1;
+ Date joinTime = null;
+
+ uid = parser.getAttributeValue("", "jid");
+
+ if (uid == null) {
+ // throw exception
+ }
+
+ eventType = parser.next();
+ while ((eventType != XmlPullParser.END_TAG)
+ || (! "user".equals(parser.getName())))
+ {
+ if ("position".equals(parser.getName())) {
+ position = Integer.parseInt(parser.nextText());
+ }
+ else if ("time".equals(parser.getName())) {
+ time = Integer.parseInt(parser.nextText());
+ }
+ else if ("join-time".equals(parser.getName())) {
+ joinTime = dateFormat.parse(parser.nextText());
+ }
+ else if( parser.getName().equals( "waitTime" ) ) {
+ Date wait = dateFormat.parse(parser.nextText());
+ System.out.println( wait );
+ }
+
+ eventType = parser.next();
+
+ if (eventType != XmlPullParser.END_TAG) {
+ // throw exception
+ }
+ }
+
+ queueDetails.addUser(new QueueUser(uid, position, time, joinTime));
+
+ eventType = parser.next();
+ }
+ }
+ return queueDetails;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java b/src/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java
new file mode 100644
index 0000000..a559579
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java
@@ -0,0 +1,160 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smackx.workgroup.agent.WorkgroupQueue;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class QueueOverview implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static String ELEMENT_NAME = "notify-queue";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss";
+ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ private int averageWaitTime;
+ private Date oldestEntry;
+ private int userCount;
+ private WorkgroupQueue.Status status;
+
+ QueueOverview() {
+ this.averageWaitTime = -1;
+ this.oldestEntry = null;
+ this.userCount = -1;
+ this.status = null;
+ }
+
+ void setAverageWaitTime(int averageWaitTime) {
+ this.averageWaitTime = averageWaitTime;
+ }
+
+ public int getAverageWaitTime () {
+ return averageWaitTime;
+ }
+
+ void setOldestEntry(Date oldestEntry) {
+ this.oldestEntry = oldestEntry;
+ }
+
+ public Date getOldestEntry() {
+ return oldestEntry;
+ }
+
+ void setUserCount(int userCount) {
+ this.userCount = userCount;
+ }
+
+ public int getUserCount() {
+ return userCount;
+ }
+
+ public WorkgroupQueue.Status getStatus() {
+ return status;
+ }
+
+ void setStatus(WorkgroupQueue.Status status) {
+ this.status = status;
+ }
+
+ public String getElementName () {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace () {
+ return NAMESPACE;
+ }
+
+ public String toXML () {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+
+ if (userCount != -1) {
+ buf.append("<count>").append(userCount).append("</count>");
+ }
+ if (oldestEntry != null) {
+ buf.append("<oldest>").append(dateFormat.format(oldestEntry)).append("</oldest>");
+ }
+ if (averageWaitTime != -1) {
+ buf.append("<time>").append(averageWaitTime).append("</time>");
+ }
+ if (status != null) {
+ buf.append("<status>").append(status).append("</status>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append(">");
+
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension (XmlPullParser parser) throws Exception {
+ int eventType = parser.getEventType();
+ QueueOverview queueOverview = new QueueOverview();
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ if (eventType != XmlPullParser.START_TAG) {
+ // throw exception
+ }
+
+ eventType = parser.next();
+ while ((eventType != XmlPullParser.END_TAG)
+ || (!ELEMENT_NAME.equals(parser.getName())))
+ {
+ if ("count".equals(parser.getName())) {
+ queueOverview.setUserCount(Integer.parseInt(parser.nextText()));
+ }
+ else if ("time".equals(parser.getName())) {
+ queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText()));
+ }
+ else if ("oldest".equals(parser.getName())) {
+ queueOverview.setOldestEntry((dateFormat.parse(parser.nextText())));
+ }
+ else if ("status".equals(parser.getName())) {
+ queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText()));
+ }
+
+ eventType = parser.next();
+
+ if (eventType != XmlPullParser.END_TAG) {
+ // throw exception
+ }
+ }
+
+ if (eventType != XmlPullParser.END_TAG) {
+ // throw exception
+ }
+
+ return queueOverview;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java b/src/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java
new file mode 100644
index 0000000..c326a57
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java
@@ -0,0 +1,122 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * An IQ packet that encapsulates both types of workgroup queue
+ * status notifications -- position updates, and estimated time
+ * left in the queue updates.
+ */
+public class QueueUpdate implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "queue-status";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private int position;
+ private int remainingTime;
+
+ public QueueUpdate(int position, int remainingTime) {
+ this.position = position;
+ this.remainingTime = remainingTime;
+ }
+
+ /**
+ * Returns the user's position in the workgroup queue, or -1 if the
+ * value isn't set on this packet.
+ *
+ * @return the position in the workgroup queue.
+ */
+ public int getPosition() {
+ return this.position;
+ }
+
+ /**
+ * Returns the user's estimated time left in the workgroup queue, or
+ * -1 if the value isn't set on this packet.
+ *
+ * @return the estimated time left in the workgroup queue.
+ */
+ public int getRemaingTime() {
+ return remainingTime;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<queue-status xmlns=\"http://jabber.org/protocol/workgroup\">");
+ if (position != -1) {
+ buf.append("<position>").append(position).append("</position>");
+ }
+ if (remainingTime != -1) {
+ buf.append("<time>").append(remainingTime).append("</time>");
+ }
+ buf.append("</queue-status>");
+ return buf.toString();
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ boolean done = false;
+ int position = -1;
+ int timeRemaining = -1;
+ while (!done) {
+ parser.next();
+ String elementName = parser.getName();
+ if (parser.getEventType() == XmlPullParser.START_TAG && "position".equals(elementName)) {
+ try {
+ position = Integer.parseInt(parser.nextText());
+ }
+ catch (NumberFormatException nfe) {
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.START_TAG && "time".equals(elementName)) {
+ try {
+ timeRemaining = Integer.parseInt(parser.nextText());
+ }
+ catch (NumberFormatException nfe) {
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.END_TAG && "queue-status".equals(elementName)) {
+ done = true;
+ }
+ }
+ return new QueueUpdate(position, timeRemaining);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/src/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java
new file mode 100644
index 0000000..34555de
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java
@@ -0,0 +1,177 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.InvitationRequest}.
+ *
+ * @author Gaston Dombiak
+ */
+public class RoomInvitation implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "invite";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ /**
+ * Type of entity being invited to a groupchat support session.
+ */
+ private Type type;
+ /**
+ * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In
+ * the case of a queue or a workgroup the server will select the best agent to invite.
+ */
+ private String invitee;
+ /**
+ * Full JID of the user that sent the invitation.
+ */
+ private String inviter;
+ /**
+ * ID of the session that originated the initial user request.
+ */
+ private String sessionID;
+ /**
+ * JID of the room to join if offer is accepted.
+ */
+ private String room;
+ /**
+ * Text provided by the inviter explaining the reason why the invitee is invited.
+ */
+ private String reason;
+
+ public RoomInvitation(Type type, String invitee, String sessionID, String reason) {
+ this.type = type;
+ this.invitee = invitee;
+ this.sessionID = sessionID;
+ this.reason = reason;
+ }
+
+ private RoomInvitation() {
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String getInviter() {
+ return inviter;
+ }
+
+ public String getRoom() {
+ return room;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE);
+ buf.append("\" type=\"").append(type).append("\">");
+ buf.append("<session xmlns=\"http://jivesoftware.com/protocol/workgroup\" id=\"").append(sessionID).append("\"></session>");
+ if (invitee != null) {
+ buf.append("<invitee>").append(invitee).append("</invitee>");
+ }
+ if (inviter != null) {
+ buf.append("<inviter>").append(inviter).append("</inviter>");
+ }
+ if (reason != null) {
+ buf.append("<reason>").append(reason).append("</reason>");
+ }
+ // Add packet extensions, if any are defined.
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * Type of entity being invited to a groupchat support session.
+ */
+ public static enum Type {
+ /**
+ * A user is being invited to a groupchat support session. The user could be another agent
+ * or just a regular XMPP user.
+ */
+ user,
+ /**
+ * Some agent of the specified queue will be invited to the groupchat support session.
+ */
+ queue,
+ /**
+ * Some agent of the specified workgroup will be invited to the groupchat support session.
+ */
+ workgroup
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ final RoomInvitation invitation = new RoomInvitation();
+ invitation.type = Type.valueOf(parser.getAttributeValue("", "type"));
+
+ boolean done = false;
+ while (!done) {
+ parser.next();
+ String elementName = parser.getName();
+ if (parser.getEventType() == XmlPullParser.START_TAG) {
+ if ("session".equals(elementName)) {
+ invitation.sessionID = parser.getAttributeValue("", "id");
+ }
+ else if ("invitee".equals(elementName)) {
+ invitation.invitee = parser.nextText();
+ }
+ else if ("inviter".equals(elementName)) {
+ invitation.inviter = parser.nextText();
+ }
+ else if ("reason".equals(elementName)) {
+ invitation.reason = parser.nextText();
+ }
+ else if ("room".equals(elementName)) {
+ invitation.room = parser.nextText();
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) {
+ done = true;
+ }
+ }
+ return invitation;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/src/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java
new file mode 100644
index 0000000..d1e83e2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java
@@ -0,0 +1,177 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.TransferRequest}.
+ *
+ * @author Gaston Dombiak
+ */
+public class RoomTransfer implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "transfer";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ /**
+ * Type of entity being invited to a groupchat support session.
+ */
+ private RoomTransfer.Type type;
+ /**
+ * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In
+ * the case of a queue or a workgroup the server will select the best agent to invite.
+ */
+ private String invitee;
+ /**
+ * Full JID of the user that sent the invitation.
+ */
+ private String inviter;
+ /**
+ * ID of the session that originated the initial user request.
+ */
+ private String sessionID;
+ /**
+ * JID of the room to join if offer is accepted.
+ */
+ private String room;
+ /**
+ * Text provided by the inviter explaining the reason why the invitee is invited.
+ */
+ private String reason;
+
+ public RoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason) {
+ this.type = type;
+ this.invitee = invitee;
+ this.sessionID = sessionID;
+ this.reason = reason;
+ }
+
+ private RoomTransfer() {
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String getInviter() {
+ return inviter;
+ }
+
+ public String getRoom() {
+ return room;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE);
+ buf.append("\" type=\"").append(type).append("\">");
+ buf.append("<session xmlns=\"http://jivesoftware.com/protocol/workgroup\" id=\"").append(sessionID).append("\"></session>");
+ if (invitee != null) {
+ buf.append("<invitee>").append(invitee).append("</invitee>");
+ }
+ if (inviter != null) {
+ buf.append("<inviter>").append(inviter).append("</inviter>");
+ }
+ if (reason != null) {
+ buf.append("<reason>").append(reason).append("</reason>");
+ }
+ // Add packet extensions, if any are defined.
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * Type of entity being invited to a groupchat support session.
+ */
+ public static enum Type {
+ /**
+ * A user is being invited to a groupchat support session. The user could be another agent
+ * or just a regular XMPP user.
+ */
+ user,
+ /**
+ * Some agent of the specified queue will be invited to the groupchat support session.
+ */
+ queue,
+ /**
+ * Some agent of the specified workgroup will be invited to the groupchat support session.
+ */
+ workgroup
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ final RoomTransfer invitation = new RoomTransfer();
+ invitation.type = RoomTransfer.Type.valueOf(parser.getAttributeValue("", "type"));
+
+ boolean done = false;
+ while (!done) {
+ parser.next();
+ String elementName = parser.getName();
+ if (parser.getEventType() == XmlPullParser.START_TAG) {
+ if ("session".equals(elementName)) {
+ invitation.sessionID = parser.getAttributeValue("", "id");
+ }
+ else if ("invitee".equals(elementName)) {
+ invitation.invitee = parser.nextText();
+ }
+ else if ("inviter".equals(elementName)) {
+ invitation.inviter = parser.nextText();
+ }
+ else if ("reason".equals(elementName)) {
+ invitation.reason = parser.nextText();
+ }
+ else if ("room".equals(elementName)) {
+ invitation.room = parser.nextText();
+ }
+ }
+ else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) {
+ done = true;
+ }
+ }
+ return invitation;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/SessionID.java b/src/org/jivesoftware/smackx/workgroup/packet/SessionID.java
new file mode 100644
index 0000000..bfd7cfd
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/SessionID.java
@@ -0,0 +1,77 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class SessionID implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "session";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ private String sessionID;
+
+ public SessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ public String getSessionID() {
+ return this.sessionID;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" ");
+ buf.append("id=\"").append(this.getSessionID());
+ buf.append("\"/>");
+
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ String sessionID = parser.getAttributeValue("", "id");
+
+ // Advance to end of extension.
+ parser.next();
+
+ return new SessionID(sessionID);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/Transcript.java b/src/org/jivesoftware/smackx/workgroup/packet/Transcript.java
new file mode 100644
index 0000000..7f8f29e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/Transcript.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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents the conversation transcript that occured in a group chat room between an Agent
+ * and a user that requested assistance. The transcript contains all the Messages that were sent
+ * to the room as well as the sent presences.
+ *
+ * @author Gaston Dombiak
+ */
+public class Transcript extends IQ {
+ private String sessionID;
+ private List<Packet> packets;
+
+ /**
+ * Creates a transcript request for the given sessionID.
+ *
+ * @param sessionID the id of the session to get the conversation transcript.
+ */
+ public Transcript(String sessionID) {
+ this.sessionID = sessionID;
+ this.packets = new ArrayList<Packet>();
+ }
+
+ /**
+ * Creates a new transcript for the given sessionID and list of packets. The list of packets
+ * may include Messages and/or Presences.
+ *
+ * @param sessionID the id of the session that generated this conversation transcript.
+ * @param packets the list of messages and presences send to the room.
+ */
+ public Transcript(String sessionID, List<Packet> packets) {
+ this.sessionID = sessionID;
+ this.packets = packets;
+ }
+
+ /**
+ * Returns id of the session that generated this conversation transcript. The sessionID is a
+ * value generated by the server when a new request is received.
+ *
+ * @return id of the session that generated this conversation transcript.
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the list of Messages and Presences that were sent to the room.
+ *
+ * @return the list of Messages and Presences that were sent to the room.
+ */
+ public List<Packet> getPackets() {
+ return Collections.unmodifiableList(packets);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<transcript xmlns=\"http://jivesoftware.com/protocol/workgroup\" sessionID=\"")
+ .append(sessionID)
+ .append("\">");
+
+ for (Iterator<Packet> it=packets.iterator(); it.hasNext();) {
+ Packet packet = it.next();
+ buf.append(packet.toXML());
+ }
+
+ buf.append("</transcript>");
+
+ return buf.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java
new file mode 100644
index 0000000..791b06e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java
@@ -0,0 +1,66 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An IQProvider for transcripts.
+ *
+ * @author Gaston Dombiak
+ */
+public class TranscriptProvider implements IQProvider {
+
+ public TranscriptProvider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ String sessionID = parser.getAttributeValue("", "sessionID");
+ List<Packet> packets = new ArrayList<Packet>();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("message")) {
+ packets.add(PacketParserUtils.parseMessage(parser));
+ }
+ else if (parser.getName().equals("presence")) {
+ packets.add(PacketParserUtils.parsePresence(parser));
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("transcript")) {
+ done = true;
+ }
+ }
+ }
+
+ return new Transcript(sessionID, packets);
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java
new file mode 100644
index 0000000..72693c4
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java
@@ -0,0 +1,87 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * IQ packet for retrieving the transcript search form, submiting the completed search form
+ * or retrieving the answer of a transcript search.
+ *
+ * @author Gaston Dombiak
+ */
+public class TranscriptSearch extends IQ {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "transcript-search";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">");
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+
+ return buf.toString();
+ }
+
+ /**
+ * An IQProvider for TranscriptSearch packets.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ TranscriptSearch answer = new TranscriptSearch();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ // Parse the packet extension
+ answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser));
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(ELEMENT_NAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return answer;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/Transcripts.java b/src/org/jivesoftware/smackx/workgroup/packet/Transcripts.java
new file mode 100644
index 0000000..66ddaad
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/Transcripts.java
@@ -0,0 +1,247 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Represents a list of conversation transcripts that a user had in all his history. Each
+ * transcript summary includes the sessionID which may be used for getting more detailed
+ * information about the conversation. {@link org.jivesoftware.smackx.workgroup.packet.Transcript}
+ *
+ * @author Gaston Dombiak
+ */
+public class Transcripts extends IQ {
+
+ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ static {
+ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
+ }
+
+ private String userID;
+ private List<Transcripts.TranscriptSummary> summaries;
+
+
+ /**
+ * Creates a transcripts request for the given userID.
+ *
+ * @param userID the id of the user to get his conversations transcripts.
+ */
+ public Transcripts(String userID) {
+ this.userID = userID;
+ this.summaries = new ArrayList<Transcripts.TranscriptSummary>();
+ }
+
+ /**
+ * Creates a Transcripts which will contain the transcript summaries of the given user.
+ *
+ * @param userID the id of the user. Could be a real JID or a unique String that identifies
+ * anonymous users.
+ * @param summaries the list of TranscriptSummaries.
+ */
+ public Transcripts(String userID, List<Transcripts.TranscriptSummary> summaries) {
+ this.userID = userID;
+ this.summaries = summaries;
+ }
+
+ /**
+ * Returns the id of the user that was involved in the conversations. The userID could be a
+ * real JID if the connected user was not anonymous. Otherwise, the userID will be a String
+ * that was provided by the anonymous user as a way to idenitify the user across many user
+ * sessions.
+ *
+ * @return the id of the user that was involved in the conversations.
+ */
+ public String getUserID() {
+ return userID;
+ }
+
+ /**
+ * Returns a list of TranscriptSummary. A TranscriptSummary does not contain the conversation
+ * transcript but some summary information like the sessionID and the time when the
+ * conversation started and finished. Once you have the sessionID it is possible to get the
+ * full conversation transcript.
+ *
+ * @return a list of TranscriptSummary.
+ */
+ public List<Transcripts.TranscriptSummary> getSummaries() {
+ return Collections.unmodifiableList(summaries);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<transcripts xmlns=\"http://jivesoftware.com/protocol/workgroup\" userID=\"")
+ .append(userID)
+ .append("\">");
+
+ for (TranscriptSummary transcriptSummary : summaries) {
+ buf.append(transcriptSummary.toXML());
+ }
+
+ buf.append("</transcripts>");
+
+ return buf.toString();
+ }
+
+ /**
+ * A TranscriptSummary contains some information about a conversation such as the ID of the
+ * session or the date when the conversation started and finished. You will need to use the
+ * sessionID to get the full conversation transcript.
+ */
+ public static class TranscriptSummary {
+ private String sessionID;
+ private Date joinTime;
+ private Date leftTime;
+ private List<AgentDetail> agentDetails;
+
+ public TranscriptSummary(String sessionID, Date joinTime, Date leftTime, List<AgentDetail> agentDetails) {
+ this.sessionID = sessionID;
+ this.joinTime = joinTime;
+ this.leftTime = leftTime;
+ this.agentDetails = agentDetails;
+ }
+
+ /**
+ * Returns the ID of the session that is related to this conversation transcript. The
+ * sessionID could be used for getting the full conversation transcript.
+ *
+ * @return the ID of the session that is related to this conversation transcript.
+ */
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ /**
+ * Returns the Date when the conversation started.
+ *
+ * @return the Date when the conversation started.
+ */
+ public Date getJoinTime() {
+ return joinTime;
+ }
+
+ /**
+ * Returns the Date when the conversation finished.
+ *
+ * @return the Date when the conversation finished.
+ */
+ public Date getLeftTime() {
+ return leftTime;
+ }
+
+ /**
+ * Returns a list of AgentDetails. For each Agent that was involved in the conversation
+ * the list will include an AgentDetail. An AgentDetail contains the JID of the agent
+ * as well as the time when the Agent joined and left the conversation.
+ *
+ * @return a list of AgentDetails.
+ */
+ public List<AgentDetail> getAgentDetails() {
+ return agentDetails;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<transcript sessionID=\"")
+ .append(sessionID)
+ .append("\">");
+
+ if (joinTime != null) {
+ buf.append("<joinTime>").append(UTC_FORMAT.format(joinTime)).append("</joinTime>");
+ }
+ if (leftTime != null) {
+ buf.append("<leftTime>").append(UTC_FORMAT.format(leftTime)).append("</leftTime>");
+ }
+ buf.append("<agents>");
+ for (AgentDetail agentDetail : agentDetails) {
+ buf.append(agentDetail.toXML());
+ }
+ buf.append("</agents></transcript>");
+
+ return buf.toString();
+ }
+ }
+
+ /**
+ * An AgentDetail contains information of an Agent that was involved in a conversation.
+ */
+ public static class AgentDetail {
+ private String agentJID;
+ private Date joinTime;
+ private Date leftTime;
+
+ public AgentDetail(String agentJID, Date joinTime, Date leftTime) {
+ this.agentJID = agentJID;
+ this.joinTime = joinTime;
+ this.leftTime = leftTime;
+ }
+
+ /**
+ * Returns the bare JID of the Agent that was involved in the conversation.
+ *
+ * @return the bared JID of the Agent that was involved in the conversation.
+ */
+ public String getAgentJID() {
+ return agentJID;
+ }
+
+ /**
+ * Returns the Date when the Agent joined the conversation.
+ *
+ * @return the Date when the Agent joined the conversation.
+ */
+ public Date getJoinTime() {
+ return joinTime;
+ }
+
+ /**
+ * Returns the Date when the Agent left the conversation.
+ *
+ * @return the Date when the Agent left the conversation.
+ */
+ public Date getLeftTime() {
+ return leftTime;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<agent>");
+
+ if (agentJID != null) {
+ buf.append("<agentJID>").append(agentJID).append("</agentJID>");
+ }
+ if (joinTime != null) {
+ buf.append("<joinTime>").append(UTC_FORMAT.format(joinTime)).append("</joinTime>");
+ }
+ if (leftTime != null) {
+ buf.append("<leftTime>").append(UTC_FORMAT.format(leftTime)).append("</leftTime>");
+ }
+ buf.append("</agent>");
+
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java
new file mode 100644
index 0000000..cb8f429
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java
@@ -0,0 +1,148 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * An IQProvider for transcripts summaries.
+ *
+ * @author Gaston Dombiak
+ */
+public class TranscriptsProvider implements IQProvider {
+
+ private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ static {
+ UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
+ }
+
+ public TranscriptsProvider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ String userID = parser.getAttributeValue("", "userID");
+ List<Transcripts.TranscriptSummary> summaries = new ArrayList<Transcripts.TranscriptSummary>();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("transcript")) {
+ summaries.add(parseSummary(parser));
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("transcripts")) {
+ done = true;
+ }
+ }
+ }
+
+ return new Transcripts(userID, summaries);
+ }
+
+ private Transcripts.TranscriptSummary parseSummary(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ String sessionID = parser.getAttributeValue("", "sessionID");
+ Date joinTime = null;
+ Date leftTime = null;
+ List<Transcripts.AgentDetail> agents = new ArrayList<Transcripts.AgentDetail>();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("joinTime")) {
+ try {
+ joinTime = UTC_FORMAT.parse(parser.nextText());
+ } catch (ParseException e) {}
+ }
+ else if (parser.getName().equals("leftTime")) {
+ try {
+ leftTime = UTC_FORMAT.parse(parser.nextText());
+ } catch (ParseException e) {}
+ }
+ else if (parser.getName().equals("agents")) {
+ agents = parseAgents(parser);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("transcript")) {
+ done = true;
+ }
+ }
+ }
+
+ return new Transcripts.TranscriptSummary(sessionID, joinTime, leftTime, agents);
+ }
+
+ private List<Transcripts.AgentDetail> parseAgents(XmlPullParser parser) throws IOException, XmlPullParserException {
+ List<Transcripts.AgentDetail> agents = new ArrayList<Transcripts.AgentDetail>();
+ String agentJID = null;
+ Date joinTime = null;
+ Date leftTime = null;
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("agentJID")) {
+ agentJID = parser.nextText();
+ }
+ else if (parser.getName().equals("joinTime")) {
+ try {
+ joinTime = UTC_FORMAT.parse(parser.nextText());
+ } catch (ParseException e) {}
+ }
+ else if (parser.getName().equals("leftTime")) {
+ try {
+ leftTime = UTC_FORMAT.parse(parser.nextText());
+ } catch (ParseException e) {}
+ }
+ else if (parser.getName().equals("agent")) {
+ agentJID = null;
+ joinTime = null;
+ leftTime = null;
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("agents")) {
+ done = true;
+ }
+ else if (parser.getName().equals("agent")) {
+ agents.add(new Transcripts.AgentDetail(agentJID, joinTime, leftTime));
+ }
+ }
+ }
+ return agents;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/UserID.java b/src/org/jivesoftware/smackx/workgroup/packet/UserID.java
new file mode 100644
index 0000000..8bf4589
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/UserID.java
@@ -0,0 +1,77 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class UserID implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "user";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ private String userID;
+
+ public UserID(String userID) {
+ this.userID = userID;
+ }
+
+ public String getUserID() {
+ return this.userID;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" ");
+ buf.append("id=\"").append(this.getUserID());
+ buf.append("\"/>");
+
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ String userID = parser.getAttributeValue("", "id");
+
+ // Advance to end of extension.
+ parser.next();
+
+ return new UserID(userID);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java b/src/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java
new file mode 100644
index 0000000..b0ea447
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java
@@ -0,0 +1,86 @@
+/**
+ * $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.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A packet extension that contains information about the user and agent in a
+ * workgroup chat. The packet extension is attached to group chat invitations.
+ */
+public class WorkgroupInformation implements PacketExtension {
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "workgroup";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
+
+ private String workgroupJID;
+
+ public WorkgroupInformation(String workgroupJID){
+ this.workgroupJID = workgroupJID;
+ }
+
+ public String getWorkgroupJID() {
+ return workgroupJID;
+ }
+
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append('<').append(ELEMENT_NAME);
+ buf.append(" jid=\"").append(getWorkgroupJID()).append("\"");
+ buf.append(" xmlns=\"").append(NAMESPACE).append("\" />");
+
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ /**
+ * PacketExtensionProvider implementation
+ */
+ public PacketExtension parseExtension (XmlPullParser parser)
+ throws Exception {
+ String workgroupJID = parser.getAttributeValue("", "jid");
+
+ // since this is a start and end tag, and we arrive on the start, this should guarantee
+ // we leave on the end
+ parser.next();
+
+ return new WorkgroupInformation(workgroupJID);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java b/src/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java
new file mode 100644
index 0000000..921134a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java
@@ -0,0 +1,56 @@
+/**
+ * $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.settings;
+
+public class ChatSetting {
+ private String key;
+ private String value;
+ private int type;
+
+ public ChatSetting(String key, String value, int type){
+ setKey(key);
+ setValue(value);
+ setType(type);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java b/src/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java
new file mode 100644
index 0000000..ccc7a40
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java
@@ -0,0 +1,179 @@
+/**
+ * $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.settings;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public class ChatSettings extends IQ {
+
+ /**
+ * Defined as image type.
+ */
+ public static final int IMAGE_SETTINGS = 0;
+
+ /**
+ * Defined as Text settings type.
+ */
+ public static final int TEXT_SETTINGS = 1;
+
+ /**
+ * Defined as Bot settings type.
+ */
+ public static final int BOT_SETTINGS = 2;
+
+ private List<ChatSetting> settings;
+ private String key;
+ private int type = -1;
+
+ public ChatSettings() {
+ settings = new ArrayList<ChatSetting>();
+ }
+
+ public ChatSettings(String key) {
+ setKey(key);
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public void addSetting(ChatSetting setting) {
+ settings.add(setting);
+ }
+
+ public Collection<ChatSetting> getSettings() {
+ return settings;
+ }
+
+ public ChatSetting getChatSetting(String key) {
+ Collection<ChatSetting> col = getSettings();
+ if (col != null) {
+ Iterator<ChatSetting> iter = col.iterator();
+ while (iter.hasNext()) {
+ ChatSetting chatSetting = iter.next();
+ if (chatSetting.getKey().equals(key)) {
+ return chatSetting;
+ }
+ }
+ }
+ return null;
+ }
+
+ public ChatSetting getFirstEntry() {
+ if (settings.size() > 0) {
+ return (ChatSetting)settings.get(0);
+ }
+ return null;
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "chat-settings";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ if (key != null) {
+ buf.append(" key=\"" + key + "\"");
+ }
+
+ if (type != -1) {
+ buf.append(" type=\"" + type + "\"");
+ }
+
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+ /**
+ * Packet extension provider for AgentStatusRequest packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ ChatSettings chatSettings = new ChatSettings();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("chat-setting".equals(parser.getName()))) {
+ chatSettings.addSetting(parseChatSetting(parser));
+
+ }
+ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return chatSettings;
+ }
+
+ private ChatSetting parseChatSetting(XmlPullParser parser) throws Exception {
+
+ boolean done = false;
+ String key = null;
+ String value = null;
+ int type = 0;
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("key".equals(parser.getName()))) {
+ key = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("value".equals(parser.getName()))) {
+ value = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("type".equals(parser.getName()))) {
+ type = Integer.parseInt(parser.nextText());
+ }
+ else if (eventType == XmlPullParser.END_TAG && "chat-setting".equals(parser.getName())) {
+ done = true;
+ }
+ }
+ return new ChatSetting(key, value, type);
+ }
+ }
+}
+
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java b/src/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java
new file mode 100644
index 0000000..702eeb7
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/GenericSettings.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.settings;
+
+import org.jivesoftware.smackx.workgroup.util.ModelUtil;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class GenericSettings extends IQ {
+
+ private Map<String, String> map = new HashMap<String, String>();
+
+ private String query;
+
+ public String getQuery() {
+ return query;
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ public Map<String, String> getMap() {
+ return map;
+ }
+
+ public void setMap(Map<String, String> map) {
+ this.map = map;
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "generic-metadata";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append(">");
+ if (ModelUtil.hasLength(getQuery())) {
+ buf.append("<query>" + getQuery() + "</query>");
+ }
+ buf.append("</").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+
+ /**
+ * Packet extension provider for SoundSetting Packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ GenericSettings setting = new GenericSettings();
+
+ boolean done = false;
+
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("entry".equals(parser.getName()))) {
+ eventType = parser.next();
+ String name = parser.nextText();
+ eventType = parser.next();
+ String value = parser.nextText();
+ setting.getMap().put(name, value);
+ }
+ else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ return setting;
+ }
+ }
+
+
+}
+
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java b/src/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java
new file mode 100644
index 0000000..15136fd
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java
@@ -0,0 +1,155 @@
+/**
+ * $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.settings;
+
+import org.jivesoftware.smackx.workgroup.util.ModelUtil;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class OfflineSettings extends IQ {
+ private String redirectURL;
+
+ private String offlineText;
+ private String emailAddress;
+ private String subject;
+
+ public String getRedirectURL() {
+ if (!ModelUtil.hasLength(redirectURL)) {
+ return "";
+ }
+ return redirectURL;
+ }
+
+ public void setRedirectURL(String redirectURL) {
+ this.redirectURL = redirectURL;
+ }
+
+ public String getOfflineText() {
+ if (!ModelUtil.hasLength(offlineText)) {
+ return "";
+ }
+ return offlineText;
+ }
+
+ public void setOfflineText(String offlineText) {
+ this.offlineText = offlineText;
+ }
+
+ public String getEmailAddress() {
+ if (!ModelUtil.hasLength(emailAddress)) {
+ return "";
+ }
+ return emailAddress;
+ }
+
+ public void setEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ }
+
+ public String getSubject() {
+ if (!ModelUtil.hasLength(subject)) {
+ return "";
+ }
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public boolean redirects() {
+ return (ModelUtil.hasLength(getRedirectURL()));
+ }
+
+ public boolean isConfigured(){
+ return ModelUtil.hasLength(getEmailAddress()) &&
+ ModelUtil.hasLength(getSubject()) &&
+ ModelUtil.hasLength(getOfflineText());
+ }
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "offline-settings";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+
+ /**
+ * Packet extension provider for AgentStatusRequest packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ OfflineSettings offlineSettings = new OfflineSettings();
+
+ boolean done = false;
+ String redirectPage = null;
+ String subject = null;
+ String offlineText = null;
+ String emailAddress = null;
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("redirectPage".equals(parser.getName()))) {
+ redirectPage = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("subject".equals(parser.getName()))) {
+ subject = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("offlineText".equals(parser.getName()))) {
+ offlineText = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("emailAddress".equals(parser.getName()))) {
+ emailAddress = parser.nextText();
+ }
+ else if (eventType == XmlPullParser.END_TAG && "offline-settings".equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ offlineSettings.setEmailAddress(emailAddress);
+ offlineSettings.setRedirectURL(redirectPage);
+ offlineSettings.setSubject(subject);
+ offlineSettings.setOfflineText(offlineText);
+ return offlineSettings;
+ }
+ }
+}
+
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java b/src/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java
new file mode 100644
index 0000000..98d59fc
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java
@@ -0,0 +1,112 @@
+/**
+ * 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.settings;
+
+import org.jivesoftware.smackx.workgroup.util.ModelUtil;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class SearchSettings extends IQ {
+ private String forumsLocation;
+ private String kbLocation;
+
+ public boolean isSearchEnabled() {
+ return ModelUtil.hasLength(getForumsLocation()) && ModelUtil.hasLength(getKbLocation());
+ }
+
+ public String getForumsLocation() {
+ return forumsLocation;
+ }
+
+ public void setForumsLocation(String forumsLocation) {
+ this.forumsLocation = forumsLocation;
+ }
+
+ public String getKbLocation() {
+ return kbLocation;
+ }
+
+ public void setKbLocation(String kbLocation) {
+ this.kbLocation = kbLocation;
+ }
+
+ public boolean hasKB(){
+ return ModelUtil.hasLength(getKbLocation());
+ }
+
+ public boolean hasForums(){
+ return ModelUtil.hasLength(getForumsLocation());
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "search-settings";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+
+ /**
+ * Packet extension provider for AgentStatusRequest packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ SearchSettings settings = new SearchSettings();
+
+ boolean done = false;
+ String kb = null;
+ String forums = null;
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("forums".equals(parser.getName()))) {
+ forums = parser.nextText();
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("kb".equals(parser.getName()))) {
+ kb = parser.nextText();
+ }
+ else if (eventType == XmlPullParser.END_TAG && "search-settings".equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ settings.setForumsLocation(forums);
+ settings.setKbLocation(kb);
+ return settings;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java b/src/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java
new file mode 100644
index 0000000..66bec35
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java
@@ -0,0 +1,103 @@
+/**
+ * $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.settings;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.StringUtils;
+import org.xmlpull.v1.XmlPullParser;
+
+public class SoundSettings extends IQ {
+ private String outgoingSound;
+ private String incomingSound;
+
+
+ public void setOutgoingSound(String outgoingSound) {
+ this.outgoingSound = outgoingSound;
+ }
+
+ public void setIncomingSound(String incomingSound) {
+ this.incomingSound = incomingSound;
+ }
+
+ public byte[] getIncomingSoundBytes() {
+ return StringUtils.decodeBase64(incomingSound);
+ }
+
+ public byte[] getOutgoingSoundBytes() {
+ return StringUtils.decodeBase64(outgoingSound);
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "sound-settings";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+
+ /**
+ * Packet extension provider for SoundSetting Packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ SoundSettings soundSettings = new SoundSettings();
+
+ boolean done = false;
+
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("outgoingSound".equals(parser.getName()))) {
+ soundSettings.setOutgoingSound(parser.nextText());
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("incomingSound".equals(parser.getName()))) {
+ soundSettings.setIncomingSound(parser.nextText());
+ }
+ else if (eventType == XmlPullParser.END_TAG && "sound-settings".equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ return soundSettings;
+ }
+ }
+}
+
diff --git a/src/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java b/src/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java
new file mode 100644
index 0000000..8e405bb
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java
@@ -0,0 +1,125 @@
+/**
+ * $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.settings;
+
+import org.jivesoftware.smackx.workgroup.util.ModelUtil;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+public class WorkgroupProperties extends IQ {
+
+ private boolean authRequired;
+ private String email;
+ private String fullName;
+ private String jid;
+
+ public boolean isAuthRequired() {
+ return authRequired;
+ }
+
+ public void setAuthRequired(boolean authRequired) {
+ this.authRequired = authRequired;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public void setFullName(String fullName) {
+ this.fullName = fullName;
+ }
+
+ public String getJid() {
+ return jid;
+ }
+
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+
+ /**
+ * Element name of the packet extension.
+ */
+ public static final String ELEMENT_NAME = "workgroup-properties";
+
+ /**
+ * Namespace of the packet extension.
+ */
+ public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup";
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=");
+ buf.append('"');
+ buf.append(NAMESPACE);
+ buf.append('"');
+ if (ModelUtil.hasLength(getJid())) {
+ buf.append("jid=\"" + getJid() + "\" ");
+ }
+ buf.append("></").append(ELEMENT_NAME).append("> ");
+ return buf.toString();
+ }
+
+ /**
+ * Packet extension provider for SoundSetting Packets.
+ */
+ public static class InternalProvider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("Parser not in proper position, or bad XML.");
+ }
+
+ WorkgroupProperties props = new WorkgroupProperties();
+
+ boolean done = false;
+
+
+ while (!done) {
+ int eventType = parser.next();
+ if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) {
+ props.setAuthRequired(new Boolean(parser.nextText()).booleanValue());
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) {
+ props.setEmail(parser.nextText());
+ }
+ else if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) {
+ props.setFullName(parser.nextText());
+ }
+ else if (eventType == XmlPullParser.END_TAG && "workgroup-properties".equals(parser.getName())) {
+ done = true;
+ }
+ }
+
+ return props;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/user/QueueListener.java b/src/org/jivesoftware/smackx/workgroup/user/QueueListener.java
new file mode 100644
index 0000000..fa3e6a6
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/user/QueueListener.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.user;
+
+/**
+ * Listener interface for those that wish to be notified of workgroup queue events.
+ *
+ * @see Workgroup#addQueueListener(QueueListener)
+ * @author loki der quaeler
+ */
+public interface QueueListener {
+
+ /**
+ * The user joined the workgroup queue.
+ */
+ public void joinedQueue();
+
+ /**
+ * The user departed the workgroup queue.
+ */
+ public void departedQueue();
+
+ /**
+ * The user's queue position has been updated to a new value.
+ *
+ * @param currentPosition the user's current position in the queue.
+ */
+ public void queuePositionUpdated(int currentPosition);
+
+ /**
+ * The user's estimated remaining wait time in the queue has been updated.
+ *
+ * @param secondsRemaining the estimated number of seconds remaining until the
+ * the user is routed to the agent.
+ */
+ public void queueWaitTimeUpdated(int secondsRemaining);
+
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/user/Workgroup.java b/src/org/jivesoftware/smackx/workgroup/user/Workgroup.java
new file mode 100644
index 0000000..237337f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/user/Workgroup.java
@@ -0,0 +1,868 @@
+/**
+ * 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.user;
+
+import org.jivesoftware.smackx.workgroup.MetaData;
+import org.jivesoftware.smackx.workgroup.WorkgroupInvitation;
+import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener;
+import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm;
+import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket;
+import org.jivesoftware.smackx.workgroup.packet.QueueUpdate;
+import org.jivesoftware.smackx.workgroup.packet.SessionID;
+import org.jivesoftware.smackx.workgroup.packet.UserID;
+import org.jivesoftware.smackx.workgroup.settings.*;
+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.FormField;
+import org.jivesoftware.smackx.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.muc.MultiUserChat;
+import org.jivesoftware.smackx.packet.DataForm;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.MUCUser;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides workgroup services for users. Users can join the workgroup queue, depart the
+ * queue, find status information about their placement in the queue, and register to
+ * be notified when they are routed to an agent.<p>
+ * <p/>
+ * This class only provides a users perspective into a workgroup and is not intended
+ * for use by agents.
+ *
+ * @author Matt Tucker
+ * @author Derek DeMoro
+ */
+public class Workgroup {
+
+ private String workgroupJID;
+ private Connection connection;
+ private boolean inQueue;
+ private List<WorkgroupInvitationListener> invitationListeners;
+ private List<QueueListener> queueListeners;
+
+ private int queuePosition = -1;
+ private int queueRemainingTime = -1;
+
+ /**
+ * Creates a new workgroup instance using the specified workgroup JID
+ * (eg support@workgroup.example.com) and XMPP connection. The connection must have
+ * undergone a successful login before being used to construct an instance of
+ * this class.
+ *
+ * @param workgroupJID the JID of the workgroup.
+ * @param connection an XMPP connection which must have already undergone a
+ * successful login.
+ */
+ public Workgroup(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;
+ inQueue = false;
+ invitationListeners = new ArrayList<WorkgroupInvitationListener>();
+ queueListeners = new ArrayList<QueueListener>();
+
+ // Register as a queue listener for internal usage by this instance.
+ addQueueListener(new QueueListener() {
+ public void joinedQueue() {
+ inQueue = true;
+ }
+
+ public void departedQueue() {
+ inQueue = false;
+ queuePosition = -1;
+ queueRemainingTime = -1;
+ }
+
+ public void queuePositionUpdated(int currentPosition) {
+ queuePosition = currentPosition;
+ }
+
+ public void queueWaitTimeUpdated(int secondsRemaining) {
+ queueRemainingTime = secondsRemaining;
+ }
+ });
+
+ /**
+ * Internal handling of an invitation.Recieving an invitation removes the user from the queue.
+ */
+ MultiUserChat.addInvitationListener(connection,
+ new org.jivesoftware.smackx.muc.InvitationListener() {
+ public void invitationReceived(Connection conn, String room, String inviter,
+ String reason, String password, Message message) {
+ inQueue = false;
+ queuePosition = -1;
+ queueRemainingTime = -1;
+ }
+ });
+
+ // Register a packet listener for all the messages sent to this client.
+ PacketFilter typeFilter = new PacketTypeFilter(Message.class);
+
+ connection.addPacketListener(new PacketListener() {
+ public void processPacket(Packet packet) {
+ handlePacket(packet);
+ }
+ }, typeFilter);
+ }
+
+ /**
+ * Returns the name of this workgroup (eg support@example.com).
+ *
+ * @return the name of the workgroup.
+ */
+ public String getWorkgroupJID() {
+ return workgroupJID;
+ }
+
+ /**
+ * Returns true if the user is currently waiting in the workgroup queue.
+ *
+ * @return true if currently waiting in the queue.
+ */
+ public boolean isInQueue() {
+ return inQueue;
+ }
+
+ /**
+ * Returns true if the workgroup is available for receiving new requests. The workgroup will be
+ * available only when agents are available for this workgroup.
+ *
+ * @return true if the workgroup is available for receiving new requests.
+ */
+ public boolean isAvailable() {
+ Presence directedPresence = new Presence(Presence.Type.available);
+ directedPresence.setTo(workgroupJID);
+ PacketFilter typeFilter = new PacketTypeFilter(Presence.class);
+ PacketFilter fromFilter = new FromContainsFilter(workgroupJID);
+ PacketCollector collector = connection.createPacketCollector(new AndFilter(fromFilter,
+ typeFilter));
+
+ connection.sendPacket(directedPresence);
+
+ Presence response = (Presence)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ return false;
+ }
+ else if (response.getError() != null) {
+ return false;
+ }
+ else {
+ return Presence.Type.available == response.getType();
+ }
+ }
+
+ /**
+ * Returns the users current position in the workgroup queue. A value of 0 means
+ * the user is next in line to be routed; therefore, if the queue position
+ * is being displayed to the end user it is usually a good idea to add 1 to
+ * the value this method returns before display. If the user is not currently
+ * waiting in the workgroup, or no queue position information is available, -1
+ * will be returned.
+ *
+ * @return the user's current position in the workgroup queue, or -1 if the
+ * position isn't available or if the user isn't in the queue.
+ */
+ public int getQueuePosition() {
+ return queuePosition;
+ }
+
+ /**
+ * Returns the estimated time (in seconds) that the user has to left wait in
+ * the workgroup queue before being routed. If the user is not currently waiting
+ * int he workgroup, or no queue time information is available, -1 will be
+ * returned.
+ *
+ * @return the estimated time remaining (in seconds) that the user has to
+ * wait inthe workgroupu queue, or -1 if time information isn't available
+ * or if the user isn't int the queue.
+ */
+ public int getQueueRemainingTime() {
+ return queueRemainingTime;
+ }
+
+ /**
+ * Joins the workgroup queue to wait to be routed to an agent. After joining
+ * the queue, queue status events will be sent to indicate the user's position and
+ * estimated time left in the queue. Once joining the queue, there are three ways
+ * the user can leave the queue: <ul>
+ * <p/>
+ * <li>The user is routed to an agent, which triggers a GroupChat invitation.
+ * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
+ * <li>A server error occurs, or an administrator explicitly removes the user
+ * from the queue.
+ * </ul>
+ * <p/>
+ * A user cannot request to join the queue again if already in the queue. Therefore,
+ * this method will throw an IllegalStateException if the user is already in the queue.<p>
+ * <p/>
+ * Some servers may be configured to require certain meta-data in order to
+ * join the queue. In that case, the {@link #joinQueue(Form)} method should be
+ * used instead of this method so that meta-data may be passed in.<p>
+ * <p/>
+ * The server tracks the conversations that a user has with agents over time. By
+ * default, that tracking is done using the user's JID. However, this is not always
+ * possible. For example, when the user is logged in anonymously using a web client.
+ * In that case the user ID might be a randomly generated value put into a persistent
+ * cookie or a username obtained via the session. A userID can be explicitly
+ * passed in by using the {@link #joinQueue(Form, String)} method. When specified,
+ * that userID will be used instead of the user's JID to track conversations. The
+ * server will ignore a manually specified userID if the user's connection to the server
+ * is not anonymous.
+ *
+ * @throws XMPPException if an error occured joining the queue. An error may indicate
+ * that a connection failure occured or that the server explicitly rejected the
+ * request to join the queue.
+ */
+ public void joinQueue() throws XMPPException {
+ joinQueue(null);
+ }
+
+ /**
+ * Joins the workgroup queue to wait to be routed to an agent. After joining
+ * the queue, queue status events will be sent to indicate the user's position and
+ * estimated time left in the queue. Once joining the queue, there are three ways
+ * the user can leave the queue: <ul>
+ * <p/>
+ * <li>The user is routed to an agent, which triggers a GroupChat invitation.
+ * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
+ * <li>A server error occurs, or an administrator explicitly removes the user
+ * from the queue.
+ * </ul>
+ * <p/>
+ * A user cannot request to join the queue again if already in the queue. Therefore,
+ * this method will throw an IllegalStateException if the user is already in the queue.<p>
+ * <p/>
+ * Some servers may be configured to require certain meta-data in order to
+ * join the queue.<p>
+ * <p/>
+ * The server tracks the conversations that a user has with agents over time. By
+ * default, that tracking is done using the user's JID. However, this is not always
+ * possible. For example, when the user is logged in anonymously using a web client.
+ * In that case the user ID might be a randomly generated value put into a persistent
+ * cookie or a username obtained via the session. A userID can be explicitly
+ * passed in by using the {@link #joinQueue(Form, String)} method. When specified,
+ * that userID will be used instead of the user's JID to track conversations. The
+ * server will ignore a manually specified userID if the user's connection to the server
+ * is not anonymous.
+ *
+ * @param answerForm the completed form the send for the join request.
+ * @throws XMPPException if an error occured joining the queue. An error may indicate
+ * that a connection failure occured or that the server explicitly rejected the
+ * request to join the queue.
+ */
+ public void joinQueue(Form answerForm) throws XMPPException {
+ joinQueue(answerForm, null);
+ }
+
+ /**
+ * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
+ * the queue, queue status events will be sent to indicate the user's position and
+ * estimated time left in the queue. Once joining the queue, there are three ways
+ * the user can leave the queue: <ul>
+ * <p/>
+ * <li>The user is routed to an agent, which triggers a GroupChat invitation.
+ * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
+ * <li>A server error occurs, or an administrator explicitly removes the user
+ * from the queue.
+ * </ul>
+ * <p/>
+ * A user cannot request to join the queue again if already in the queue. Therefore,
+ * this method will throw an IllegalStateException if the user is already in the queue.<p>
+ * <p/>
+ * Some servers may be configured to require certain meta-data in order to
+ * join the queue.<p>
+ * <p/>
+ * The server tracks the conversations that a user has with agents over time. By
+ * default, that tracking is done using the user's JID. However, this is not always
+ * possible. For example, when the user is logged in anonymously using a web client.
+ * In that case the user ID might be a randomly generated value put into a persistent
+ * cookie or a username obtained via the session. When specified, that userID will
+ * be used instead of the user's JID to track conversations. The server will ignore a
+ * manually specified userID if the user's connection to the server is not anonymous.
+ *
+ * @param answerForm the completed form associated with the join reqest.
+ * @param userID String that represents the ID of the user when using anonymous sessions
+ * or <tt>null</tt> if a userID should not be used.
+ * @throws XMPPException if an error occured joining the queue. An error may indicate
+ * that a connection failure occured or that the server explicitly rejected the
+ * request to join the queue.
+ */
+ public void joinQueue(Form answerForm, String userID) throws XMPPException {
+ // If already in the queue ignore the join request.
+ if (inQueue) {
+ throw new IllegalStateException("Already in queue " + workgroupJID);
+ }
+
+ JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID);
+
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(joinPacket.getPacketID()));
+
+ this.connection.sendPacket(joinPacket);
+
+ IQ response = (IQ)collector.nextResult(10000);
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from the server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+
+ // Notify listeners that we've joined the queue.
+ fireQueueJoinedEvent();
+ }
+
+ /**
+ * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
+ * the queue, queue status events will be sent to indicate the user's position and
+ * estimated time left in the queue. Once joining the queue, there are three ways
+ * the user can leave the queue: <ul>
+ * <p/>
+ * <li>The user is routed to an agent, which triggers a GroupChat invitation.
+ * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
+ * <li>A server error occurs, or an administrator explicitly removes the user
+ * from the queue.
+ * </ul>
+ * <p/>
+ * A user cannot request to join the queue again if already in the queue. Therefore,
+ * this method will throw an IllegalStateException if the user is already in the queue.<p>
+ * <p/>
+ * Some servers may be configured to require certain meta-data in order to
+ * join the queue.<p>
+ * <p/>
+ * The server tracks the conversations that a user has with agents over time. By
+ * default, that tracking is done using the user's JID. However, this is not always
+ * possible. For example, when the user is logged in anonymously using a web client.
+ * In that case the user ID might be a randomly generated value put into a persistent
+ * cookie or a username obtained via the session. When specified, that userID will
+ * be used instead of the user's JID to track conversations. The server will ignore a
+ * manually specified userID if the user's connection to the server is not anonymous.
+ *
+ * @param metadata metadata to create a dataform from.
+ * @param userID String that represents the ID of the user when using anonymous sessions
+ * or <tt>null</tt> if a userID should not be used.
+ * @throws XMPPException if an error occured joining the queue. An error may indicate
+ * that a connection failure occured or that the server explicitly rejected the
+ * request to join the queue.
+ */
+ public void joinQueue(Map<String,Object> metadata, String userID) throws XMPPException {
+ // If already in the queue ignore the join request.
+ if (inQueue) {
+ throw new IllegalStateException("Already in queue " + workgroupJID);
+ }
+
+ // Build dataform from metadata
+ Form form = new Form(Form.TYPE_SUBMIT);
+ Iterator<String> iter = metadata.keySet().iterator();
+ while (iter.hasNext()) {
+ String name = iter.next();
+ String value = metadata.get(name).toString();
+
+ String escapedName = StringUtils.escapeForXML(name);
+ String escapedValue = StringUtils.escapeForXML(value);
+
+ FormField field = new FormField(escapedName);
+ field.setType(FormField.TYPE_TEXT_SINGLE);
+ form.addField(field);
+ form.setAnswer(escapedName, escapedValue);
+ }
+ joinQueue(form, userID);
+ }
+
+ /**
+ * Departs the workgroup queue. If the user is not currently in the queue, this
+ * method will do nothing.<p>
+ * <p/>
+ * Normally, the user would not manually leave the queue. However, they may wish to
+ * under certain circumstances -- for example, if they no longer wish to be routed
+ * to an agent because they've been waiting too long.
+ *
+ * @throws XMPPException if an error occured trying to send the depart queue
+ * request to the server.
+ */
+ public void departQueue() throws XMPPException {
+ // If not in the queue ignore the depart request.
+ if (!inQueue) {
+ return;
+ }
+
+ DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
+ PacketCollector collector = this.connection.createPacketCollector(new PacketIDFilter(departPacket.getPacketID()));
+
+ connection.sendPacket(departPacket);
+
+ IQ response = (IQ)collector.nextResult(5000);
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from the server.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+
+ // Notify listeners that we're no longer in the queue.
+ fireQueueDepartedEvent();
+ }
+
+ /**
+ * Adds a queue listener that will be notified of queue events for the user
+ * that created this Workgroup instance.
+ *
+ * @param queueListener the queue listener.
+ */
+ public void addQueueListener(QueueListener queueListener) {
+ synchronized (queueListeners) {
+ if (!queueListeners.contains(queueListener)) {
+ queueListeners.add(queueListener);
+ }
+ }
+ }
+
+ /**
+ * Removes a queue listener.
+ *
+ * @param queueListener the queue listener.
+ */
+ public void removeQueueListener(QueueListener queueListener) {
+ synchronized (queueListeners) {
+ queueListeners.remove(queueListener);
+ }
+ }
+
+ /**
+ * Adds an invitation listener that will be notified of groupchat invitations
+ * from the workgroup for the the user that created this Workgroup instance.
+ *
+ * @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 removeQueueListener(WorkgroupInvitationListener invitationListener) {
+ synchronized (invitationListeners) {
+ invitationListeners.remove(invitationListener);
+ }
+ }
+
+ private void fireInvitationEvent(WorkgroupInvitation invitation) {
+ synchronized (invitationListeners) {
+ for (Iterator<WorkgroupInvitationListener> i = invitationListeners.iterator(); i.hasNext();) {
+ WorkgroupInvitationListener listener = i.next();
+ listener.invitationReceived(invitation);
+ }
+ }
+ }
+
+ private void fireQueueJoinedEvent() {
+ synchronized (queueListeners) {
+ for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
+ QueueListener listener = i.next();
+ listener.joinedQueue();
+ }
+ }
+ }
+
+ private void fireQueueDepartedEvent() {
+ synchronized (queueListeners) {
+ for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
+ QueueListener listener = i.next();
+ listener.departedQueue();
+ }
+ }
+ }
+
+ private void fireQueuePositionEvent(int currentPosition) {
+ synchronized (queueListeners) {
+ for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
+ QueueListener listener = i.next();
+ listener.queuePositionUpdated(currentPosition);
+ }
+ }
+ }
+
+ private void fireQueueTimeEvent(int secondsRemaining) {
+ synchronized (queueListeners) {
+ for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
+ QueueListener listener = i.next();
+ listener.queueWaitTimeUpdated(secondsRemaining);
+ }
+ }
+ }
+
+ // PacketListener Implementation.
+
+ private void handlePacket(Packet packet) {
+ if (packet instanceof Message) {
+ Message msg = (Message)packet;
+ // Check to see if the user left the queue.
+ PacketExtension pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup");
+ PacketExtension queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup");
+
+ if (pe != null) {
+ fireQueueDepartedEvent();
+ }
+ else if (queueStatus != null) {
+ QueueUpdate queueUpdate = (QueueUpdate)queueStatus;
+ if (queueUpdate.getPosition() != -1) {
+ fireQueuePositionEvent(queueUpdate.getPosition());
+ }
+ if (queueUpdate.getRemaingTime() != -1) {
+ fireQueueTimeEvent(queueUpdate.getRemaingTime());
+ }
+ }
+
+ else {
+ // Check if a room invitation was sent and if the sender is the workgroup
+ MUCUser mucUser = (MUCUser)msg.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;
+
+ pe = msg.getExtension(SessionID.ELEMENT_NAME,
+ SessionID.NAMESPACE);
+ if (pe != null) {
+ sessionID = ((SessionID)pe).getSessionID();
+ }
+
+ pe = msg.getExtension(MetaData.ELEMENT_NAME,
+ MetaData.NAMESPACE);
+ if (pe != null) {
+ metaData = ((MetaData)pe).getMetaData();
+ }
+
+ WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(),
+ workgroupJID, sessionID, msg.getBody(),
+ msg.getFrom(), metaData);
+
+ fireInvitationEvent(inv);
+ }
+ }
+ }
+ }
+
+ /**
+ * IQ packet to request joining the workgroup queue.
+ */
+ private class JoinQueuePacket extends IQ {
+
+ private String userID = null;
+ private DataForm form;
+
+ public JoinQueuePacket(String workgroup, Form answerForm, String userID) {
+ this.userID = userID;
+
+ setTo(workgroup);
+ setType(IQ.Type.SET);
+
+ form = answerForm.getDataFormToSend();
+ addExtension(form);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("<join-queue xmlns=\"http://jabber.org/protocol/workgroup\">");
+ buf.append("<queue-notifications/>");
+ // Add the user unique identification if the session is anonymous
+ if (connection.isAnonymous()) {
+ buf.append(new UserID(userID).toXML());
+ }
+
+ // Append data form text
+ buf.append(form.toXML());
+
+ buf.append("</join-queue>");
+
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Returns a single chat setting based on it's identified key.
+ *
+ * @param key the key to find.
+ * @return the ChatSetting if found, otherwise false.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public ChatSetting getChatSetting(String key) throws XMPPException {
+ ChatSettings chatSettings = getChatSettings(key, -1);
+ return chatSettings.getFirstEntry();
+ }
+
+ /**
+ * Returns ChatSettings based on type.
+ *
+ * @param type the type of ChatSettings to return.
+ * @return the ChatSettings of given type, otherwise null.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public ChatSettings getChatSettings(int type) throws XMPPException {
+ return getChatSettings(null, type);
+ }
+
+ /**
+ * Returns all ChatSettings.
+ *
+ * @return all ChatSettings of a given workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public ChatSettings getChatSettings() throws XMPPException {
+ return getChatSettings(null, -1);
+ }
+
+
+ /**
+ * Asks the workgroup for it's Chat Settings.
+ *
+ * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ private ChatSettings getChatSettings(String key, int type) throws XMPPException {
+ ChatSettings request = new ChatSettings();
+ if (key != null) {
+ request.setKey(key);
+ }
+ if (type != -1) {
+ request.setType(type);
+ }
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ ChatSettings response = (ChatSettings)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;
+ }
+
+ /**
+ * The workgroup service may be configured to send email. This queries the Workgroup Service
+ * to see if the email service has been configured and is available.
+ *
+ * @return true if the email service is available, otherwise return false.
+ */
+ public boolean isEmailAvailable() {
+ ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+
+ try {
+ String workgroupService = StringUtils.parseServer(workgroupJID);
+ DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService);
+ return infoResult.containsFeature("jive:email:provider");
+ }
+ catch (XMPPException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Asks the workgroup for it's Offline Settings.
+ *
+ * @return offlineSettings the offline settings for this workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public OfflineSettings getOfflineSettings() throws XMPPException {
+ OfflineSettings request = new OfflineSettings();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ OfflineSettings response = (OfflineSettings)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 Sound Settings.
+ *
+ * @return soundSettings the sound settings for the specified workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public SoundSettings getSoundSettings() throws XMPPException {
+ SoundSettings request = new SoundSettings();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ SoundSettings response = (SoundSettings)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 Properties
+ *
+ * @return the WorkgroupProperties for the specified workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public WorkgroupProperties getWorkgroupProperties() throws XMPPException {
+ WorkgroupProperties request = new WorkgroupProperties();
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ WorkgroupProperties response = (WorkgroupProperties)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 Properties
+ *
+ * @param jid the jid of the user who's information you would like the workgroup to retreive.
+ * @return the WorkgroupProperties for the specified workgroup.
+ * @throws XMPPException if an error occurs while getting information from the server.
+ */
+ public WorkgroupProperties getWorkgroupProperties(String jid) throws XMPPException {
+ WorkgroupProperties request = new WorkgroupProperties();
+ request.setJid(jid);
+ request.setType(IQ.Type.GET);
+ request.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
+ connection.sendPacket(request);
+
+
+ WorkgroupProperties response = (WorkgroupProperties)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;
+ }
+
+
+ /**
+ * Returns the Form to use for all clients of a workgroup. 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 getWorkgroupForm() throws XMPPException {
+ WorkgroupForm workgroupForm = new WorkgroupForm();
+ workgroupForm.setType(IQ.Type.GET);
+ workgroupForm.setTo(workgroupJID);
+
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(workgroupForm.getPacketID()));
+ connection.sendPacket(workgroupForm);
+
+ WorkgroupForm response = (WorkgroupForm)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);
+ }
+
+ /*
+ public static void main(String args[]) throws Exception {
+ Connection con = new XMPPConnection("anteros");
+ con.connect();
+ con.loginAnonymously();
+
+ Workgroup workgroup = new Workgroup("demo@workgroup.anteros", con);
+ WorkgroupProperties props = workgroup.getWorkgroupProperties("derek@anteros.com");
+
+ System.out.print(props);
+ con.disconnect();
+ }
+ */
+
+
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java b/src/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java
new file mode 100644
index 0000000..533b9a1
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java
@@ -0,0 +1,132 @@
+/**
+ * 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.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.ListIterator;
+
+/**
+ * This class is a very flexible event dispatcher which implements Runnable so that it can
+ * dispatch easily from a newly created thread. The usage of this in code is more or less:
+ * create a new instance of this class, use addListenerTriplet to add as many listeners
+ * as desired to be messaged, create a new Thread using the instance of this class created
+ * as the argument to the constructor, start the new Thread instance.<p>
+ *
+ * Also, this is intended to be used to message methods that either return void, or have
+ * a return which the developer using this class is uninterested in receiving.
+ *
+ * @author loki der quaeler
+ */
+public class ListenerEventDispatcher
+ implements Runnable {
+
+ protected transient ArrayList<TripletContainer> triplets;
+
+ protected transient boolean hasFinishedDispatching;
+ protected transient boolean isRunning;
+
+ public ListenerEventDispatcher () {
+ super();
+
+ this.triplets = new ArrayList<TripletContainer>();
+
+ this.hasFinishedDispatching = false;
+ this.isRunning = false;
+ }
+
+ /**
+ * Add a listener triplet - the instance of the listener to be messaged, the Method on which
+ * the listener should be messaged, and the Object array of arguments to be supplied to the
+ * Method. No attempts are made to determine whether this triplet was already added.<br>
+ *
+ * Messages are dispatched in the order in which they're added via this method; so if triplet
+ * X is added after triplet Z, then triplet Z will undergo messaging prior to triplet X.<br>
+ *
+ * This method should not be called once the owning Thread instance has been started; if it
+ * is called, the triplet will not be added to the messaging queue.<br>
+ *
+ * @param listenerInstance the instance of the listener to receive the associated notification
+ * @param listenerMethod the Method instance representing the method through which notification
+ * will occur
+ * @param methodArguments the arguments supplied to the notification method
+ */
+ public void addListenerTriplet(Object listenerInstance, Method listenerMethod,
+ Object[] methodArguments)
+ {
+ if (!this.isRunning) {
+ this.triplets.add(new TripletContainer(listenerInstance, listenerMethod,
+ methodArguments));
+ }
+ }
+
+ /**
+ * @return whether this instance has finished dispatching its messages
+ */
+ public boolean hasFinished() {
+ return this.hasFinishedDispatching;
+ }
+
+ public void run() {
+ ListIterator<TripletContainer> li = null;
+
+ this.isRunning = true;
+
+ li = this.triplets.listIterator();
+ while (li.hasNext()) {
+ TripletContainer tc = li.next();
+
+ try {
+ tc.getListenerMethod().invoke(tc.getListenerInstance(), tc.getMethodArguments());
+ } catch (Exception e) {
+ System.err.println("Exception dispatching an event: " + e);
+
+ e.printStackTrace();
+ }
+ }
+
+ this.hasFinishedDispatching = true;
+ }
+
+
+ protected class TripletContainer {
+
+ protected Object listenerInstance;
+ protected Method listenerMethod;
+ protected Object[] methodArguments;
+
+ protected TripletContainer (Object inst, Method meth, Object[] args) {
+ super();
+
+ this.listenerInstance = inst;
+ this.listenerMethod = meth;
+ this.methodArguments = args;
+ }
+
+ protected Object getListenerInstance() {
+ return this.listenerInstance;
+ }
+
+ protected Method getListenerMethod() {
+ return this.listenerMethod;
+ }
+
+ protected Object[] getMethodArguments() {
+ return this.methodArguments;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java b/src/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java
new file mode 100644
index 0000000..5be1c1a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java
@@ -0,0 +1,103 @@
+/**
+ * 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.util;
+
+import org.jivesoftware.smackx.workgroup.MetaData;
+import org.jivesoftware.smack.util.StringUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Utility class for meta-data parsing and writing.
+ *
+ * @author Matt Tucker
+ */
+public class MetaDataUtils {
+
+ /**
+ * Parses any available meta-data and returns it as a Map of String name/value pairs. The
+ * parser must be positioned at an opening meta-data tag, or the an empty map will be returned.
+ *
+ * @param parser the XML parser positioned at an opening meta-data tag.
+ * @return the meta-data.
+ * @throws XmlPullParserException if an error occurs while parsing the XML.
+ * @throws IOException if an error occurs while parsing the XML.
+ */
+ public static Map<String, List<String>> parseMetaData(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int eventType = parser.getEventType();
+
+ // If correctly positioned on an opening meta-data tag, parse meta-data.
+ if ((eventType == XmlPullParser.START_TAG)
+ && parser.getName().equals(MetaData.ELEMENT_NAME)
+ && parser.getNamespace().equals(MetaData.NAMESPACE)) {
+ Map<String, List<String>> metaData = new Hashtable<String, List<String>>();
+
+ eventType = parser.nextTag();
+
+ // Keep parsing until we've gotten to end of meta-data.
+ while ((eventType != XmlPullParser.END_TAG)
+ || (!parser.getName().equals(MetaData.ELEMENT_NAME))) {
+ String name = parser.getAttributeValue(0);
+ String value = parser.nextText();
+
+ if (metaData.containsKey(name)) {
+ List<String> values = metaData.get(name);
+ values.add(value);
+ }
+ else {
+ List<String> values = new ArrayList<String>();
+ values.add(value);
+ metaData.put(name, values);
+ }
+
+ eventType = parser.nextTag();
+ }
+
+ return metaData;
+ }
+
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Serializes a Map of String name/value pairs into the meta-data XML format.
+ *
+ * @param metaData the Map of meta-data as Map&lt;String,List&lt;String>>
+ * @return the meta-data values in XML form.
+ */
+ public static String serializeMetaData(Map<String, List<String>> metaData) {
+ StringBuilder buf = new StringBuilder();
+ if (metaData != null && metaData.size() > 0) {
+ buf.append("<metadata xmlns=\"http://jivesoftware.com/protocol/workgroup\">");
+ for (Iterator<String> i = metaData.keySet().iterator(); i.hasNext();) {
+ String key = i.next();
+ List<String> value = metaData.get(key);
+ for (Iterator<String> it = value.iterator(); it.hasNext();) {
+ String v = it.next();
+ buf.append("<value name=\"").append(key).append("\">");
+ buf.append(StringUtils.escapeForXML(v));
+ buf.append("</value>");
+ }
+ }
+ buf.append("</metadata>");
+ }
+ return buf.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/workgroup/util/ModelUtil.java b/src/org/jivesoftware/smackx/workgroup/util/ModelUtil.java
new file mode 100644
index 0000000..0a4df15
--- /dev/null
+++ b/src/org/jivesoftware/smackx/workgroup/util/ModelUtil.java
@@ -0,0 +1,322 @@
+/**
+ * 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.util;
+
+import java.util.*;
+
+/**
+ * Utility methods frequently used by data classes and design-time
+ * classes.
+ */
+public final class ModelUtil {
+ private ModelUtil() {
+ // Prevents instantiation.
+ }
+
+ /**
+ * This is a utility method that compares two objects when one or
+ * both of the objects might be <CODE>null</CODE> The result of
+ * this method is determined as follows:
+ * <OL>
+ * <LI>If <CODE>o1</CODE> and <CODE>o2</CODE> are the same object
+ * according to the <CODE>==</CODE> operator, return
+ * <CODE>true</CODE>.
+ * <LI>Otherwise, if either <CODE>o1</CODE> or <CODE>o2</CODE> is
+ * <CODE>null</CODE>, return <CODE>false</CODE>.
+ * <LI>Otherwise, return <CODE>o1.equals(o2)</CODE>.
+ * </OL>
+ * <p/>
+ * This method produces the exact logically inverted result as the
+ * {@link #areDifferent(Object, Object)} method.<P>
+ * <p/>
+ * For array types, one of the <CODE>equals</CODE> methods in
+ * {@link java.util.Arrays} should be used instead of this method.
+ * Note that arrays with more than one dimension will require some
+ * custom code in order to implement <CODE>equals</CODE> properly.
+ */
+ public static final boolean areEqual(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ else if (o1 == null || o2 == null) {
+ return false;
+ }
+ else {
+ return o1.equals(o2);
+ }
+ }
+
+ /**
+ * This is a utility method that compares two Booleans when one or
+ * both of the objects might be <CODE>null</CODE> The result of
+ * this method is determined as follows:
+ * <OL>
+ * <LI>If <CODE>b1</CODE> and <CODE>b2</CODE> are both TRUE or
+ * neither <CODE>b1</CODE> nor <CODE>b2</CODE> is TRUE,
+ * return <CODE>true</CODE>.
+ * <LI>Otherwise, return <CODE>false</CODE>.
+ * </OL>
+ * <p/>
+ */
+ public static final boolean areBooleansEqual(Boolean b1, Boolean b2) {
+ // !jwetherb treat NULL the same as Boolean.FALSE
+ return (b1 == Boolean.TRUE && b2 == Boolean.TRUE) ||
+ (b1 != Boolean.TRUE && b2 != Boolean.TRUE);
+ }
+
+ /**
+ * This is a utility method that compares two objects when one or
+ * both of the objects might be <CODE>null</CODE>. The result
+ * returned by this method is determined as follows:
+ * <OL>
+ * <LI>If <CODE>o1</CODE> and <CODE>o2</CODE> are the same object
+ * according to the <CODE>==</CODE> operator, return
+ * <CODE>false</CODE>.
+ * <LI>Otherwise, if either <CODE>o1</CODE> or <CODE>o2</CODE> is
+ * <CODE>null</CODE>, return <CODE>true</CODE>.
+ * <LI>Otherwise, return <CODE>!o1.equals(o2)</CODE>.
+ * </OL>
+ * <p/>
+ * This method produces the exact logically inverted result as the
+ * {@link #areEqual(Object, Object)} method.<P>
+ * <p/>
+ * For array types, one of the <CODE>equals</CODE> methods in
+ * {@link java.util.Arrays} should be used instead of this method.
+ * Note that arrays with more than one dimension will require some
+ * custom code in order to implement <CODE>equals</CODE> properly.
+ */
+ public static final boolean areDifferent(Object o1, Object o2) {
+ return !areEqual(o1, o2);
+ }
+
+
+ /**
+ * This is a utility method that compares two Booleans when one or
+ * both of the objects might be <CODE>null</CODE> The result of
+ * this method is determined as follows:
+ * <OL>
+ * <LI>If <CODE>b1</CODE> and <CODE>b2</CODE> are both TRUE or
+ * neither <CODE>b1</CODE> nor <CODE>b2</CODE> is TRUE,
+ * return <CODE>false</CODE>.
+ * <LI>Otherwise, return <CODE>true</CODE>.
+ * </OL>
+ * <p/>
+ * This method produces the exact logically inverted result as the
+ * {@link #areBooleansEqual(Boolean, Boolean)} method.<P>
+ */
+ public static final boolean areBooleansDifferent(Boolean b1, Boolean b2) {
+ return !areBooleansEqual(b1, b2);
+ }
+
+
+ /**
+ * Returns <CODE>true</CODE> if the specified array is not null
+ * and contains a non-null element. Returns <CODE>false</CODE>
+ * if the array is null or if all the array elements are null.
+ */
+ public static final boolean hasNonNullElement(Object[] array) {
+ if (array != null) {
+ final int n = array.length;
+ for (int i = 0; i < n; i++) {
+ if (array[i] != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a single string that is the concatenation of all the
+ * strings in the specified string array. A single space is
+ * put between each string array element. Null array elements
+ * are skipped. If the array itself is null, the empty string
+ * is returned. This method is guaranteed to return a non-null
+ * value, if no expections are thrown.
+ */
+ public static final String concat(String[] strs) {
+ return concat(strs, " "); //NOTRANS
+ }
+
+ /**
+ * Returns a single string that is the concatenation of all the
+ * strings in the specified string array. The strings are separated
+ * by the specified delimiter. Null array elements are skipped. If
+ * the array itself is null, the empty string is returned. This
+ * method is guaranteed to return a non-null value, if no expections
+ * are thrown.
+ */
+ public static final String concat(String[] strs, String delim) {
+ if (strs != null) {
+ final StringBuilder buf = new StringBuilder();
+ final int n = strs.length;
+ for (int i = 0; i < n; i++) {
+ final String str = strs[i];
+ if (str != null) {
+ buf.append(str).append(delim);
+ }
+ }
+ final int length = buf.length();
+ if (length > 0) {
+ // Trim trailing space.
+ buf.setLength(length - 1);
+ }
+ return buf.toString();
+ }
+ else {
+ return ""; // NOTRANS
+ }
+ }
+
+ /**
+ * Returns <CODE>true</CODE> if the specified {@link String} is not
+ * <CODE>null</CODE> and has a length greater than zero. This is
+ * a very frequently occurring check.
+ */
+ public static final boolean hasLength(String s) {
+ return (s != null && s.length() > 0);
+ }
+
+
+ /**
+ * Returns <CODE>null</CODE> if the specified string is empty or
+ * <CODE>null</CODE>. Otherwise the string itself is returned.
+ */
+ public static final String nullifyIfEmpty(String s) {
+ return ModelUtil.hasLength(s) ? s : null;
+ }
+
+ /**
+ * Returns <CODE>null</CODE> if the specified object is null
+ * or if its <CODE>toString()</CODE> representation is empty.
+ * Otherwise, the <CODE>toString()</CODE> representation of the
+ * object itself is returned.
+ */
+ public static final String nullifyingToString(Object o) {
+ return o != null ? nullifyIfEmpty(o.toString()) : null;
+ }
+
+ /**
+ * Determines if a string has been changed.
+ *
+ * @param oldString is the initial value of the String
+ * @param newString is the new value of the String
+ * @return true If both oldString and newString are null or if they are
+ * both not null and equal to each other. Otherwise returns false.
+ */
+ public static boolean hasStringChanged(String oldString, String newString) {
+ if (oldString == null && newString == null) {
+ return false;
+ }
+ else if ((oldString == null && newString != null)
+ || (oldString != null && newString == null)) {
+ return true;
+ }
+ else {
+ return !oldString.equals(newString);
+ }
+ }
+
+ public static String getTimeFromLong(long diff) {
+ final String HOURS = "h";
+ final String MINUTES = "min";
+ final String SECONDS = "sec";
+
+ final long MS_IN_A_DAY = 1000 * 60 * 60 * 24;
+ final long MS_IN_AN_HOUR = 1000 * 60 * 60;
+ final long MS_IN_A_MINUTE = 1000 * 60;
+ final long MS_IN_A_SECOND = 1000;
+ diff = diff % MS_IN_A_DAY;
+ long numHours = diff / MS_IN_AN_HOUR;
+ diff = diff % MS_IN_AN_HOUR;
+ long numMinutes = diff / MS_IN_A_MINUTE;
+ diff = diff % MS_IN_A_MINUTE;
+ long numSeconds = diff / MS_IN_A_SECOND;
+ diff = diff % MS_IN_A_SECOND;
+
+ StringBuilder buf = new StringBuilder();
+ if (numHours > 0) {
+ buf.append(numHours + " " + HOURS + ", ");
+ }
+
+ if (numMinutes > 0) {
+ buf.append(numMinutes + " " + MINUTES + ", ");
+ }
+
+ buf.append(numSeconds + " " + SECONDS);
+
+ String result = buf.toString();
+ return result;
+ }
+
+
+ /**
+ * Build a List of all elements in an Iterator.
+ */
+ public static <T> List<T> iteratorAsList(Iterator<T> i) {
+ ArrayList<T> list = new ArrayList<T>(10);
+ while (i.hasNext()) {
+ list.add(i.next());
+ }
+ return list;
+ }
+
+ /**
+ * Creates an Iterator that is the reverse of a ListIterator.
+ */
+ public static <T> Iterator<T> reverseListIterator(ListIterator<T> i) {
+ return new ReverseListIterator<T>(i);
+ }
+}
+
+/**
+ * An Iterator that is the reverse of a ListIterator.
+ */
+class ReverseListIterator<T> implements Iterator<T> {
+ private ListIterator<T> _i;
+
+ ReverseListIterator(ListIterator<T> i) {
+ _i = i;
+ while (_i.hasNext())
+ _i.next();
+ }
+
+ public boolean hasNext() {
+ return _i.hasPrevious();
+ }
+
+ public T next() {
+ return _i.previous();
+ }
+
+ public void remove() {
+ _i.remove();
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+