diff --git a/source/org/jivesoftware/smackx/workgroup/MetaData.java b/source/org/jivesoftware/smackx/workgroup/MetaData.java new file mode 100644 index 000000000..7673835e5 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/MetaData.java @@ -0,0 +1,67 @@ +/** + * $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.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 metaData; + + public MetaData(Map metaData) { + this.metaData = metaData; + } + + /** + * @return the Map of metadata contained by this instance + */ + public Map 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/source/org/jivesoftware/smackx/workgroup/QueueUser.java b/source/org/jivesoftware/smackx/workgroup/QueueUser.java new file mode 100644 index 000000000..89a18995f --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java b/source/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java new file mode 100644 index 000000000..8409966de --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java @@ -0,0 +1,133 @@ +/** + * $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.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 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 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 getMetaData () { + return this.metaData; + } + +} diff --git a/source/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java b/source/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java new file mode 100644 index 000000000..bc7324252 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/Agent.java b/source/org/jivesoftware/smackx/workgroup/agent/Agent.java new file mode 100644 index 000000000..39c2fac6a --- /dev/null +++ b/source/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.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; + +import java.util.Collection; + +/** + * The Agent class is used to represent one agent in a Workgroup Queue. + * + * @author Derek DeMoro + */ +public class Agent { + private XMPPConnection connection; + private String workgroupJID; + + public static Collection getWorkgroups(String serviceJID, String agentJID, XMPPConnection 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(XMPPConnection 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/source/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java b/source/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java new file mode 100644 index 000000000..fb62f189c --- /dev/null +++ b/source/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.XMPPConnection; +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 XMPPConnection connection; + private String workgroupJID; + private List entries; + private List listeners; + private Map 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(XMPPConnection connection, String workgroupJID) { + this.connection = connection; + this.workgroupJID = workgroupJID; + entries = new ArrayList(); + listeners = new ArrayList(); + presenceMap = new HashMap(); + // 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 it = getAgents().iterator(); it.hasNext();) { + String jid = (String)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 userPresences = (Map)presenceMap.get(jid); + if (userPresences != null) { + Iterator presences = userPresences.values().iterator(); + while (presences.hasNext()) { + // Fire the presence changed event + listener.presenceChanged((Presence)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 getAgents() { + Set agents = new HashSet(); + synchronized (entries) { + for (Iterator 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 i = entries.iterator(); i.hasNext();) { + String entry = (String)i.next(); + if (entry.toLowerCase().equals(jid.toLowerCase())) { + return true; + } + } + } + return false; + } + + /** + * Returns the presence info for a particular agent, or null if the agent + * is unavailable (offline) or if no presence information is available.

+ * + * @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 null if the agent is unavailable + * or if no presence information is available.. + */ + public Presence getPresence(String user) { + String key = getPresenceMapKey(user); + Map userPresences = (Map)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 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 userPresences; + // Get the user presence map + if (presenceMap.get(key) == null) { + userPresences = new HashMap(); + presenceMap.put(key, userPresences); + } + else { + userPresences = (Map)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 i = entries.iterator(); i.hasNext();) { + String entry = (String)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 userPresences = (Map)presenceMap.get(key); + synchronized (userPresences) { + userPresences.remove(StringUtils.parseResource(from)); + } + if (userPresences.isEmpty()) { + presenceMap.remove(key); + } + } + // Fire an event. + synchronized (entries) { + for (Iterator 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 i = statusRequest.getAgents().iterator(); i.hasNext();) { + AgentStatusRequest.Item item = (AgentStatusRequest.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/source/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java b/source/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java new file mode 100644 index 000000000..4db920377 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/AgentSession.java b/source/org/jivesoftware/smackx/workgroup/agent/AgentSession.java new file mode 100644 index 000000000..72170297f --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/agent/AgentSession.java @@ -0,0 +1,1184 @@ +/** + * $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.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 XMPPConnection connection; + + private String workgroupJID; + + private boolean online = false; + private Presence.Mode presenceMode; + private int maxChats; + private final Map metaData; + + private Map queues; + + private final List offerListeners; + private final List invitationListeners; + private final List 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 true 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, XMPPConnection connection) { + // Login must have been done before passing in connection. + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must login to server before creating workgroup."); + } + + this.workgroupJID = workgroupJID; + this.connection = connection; + this.transcriptManager = new TranscriptManager(connection); + this.transcriptSearchManager = new TranscriptSearchManager(connection); + + this.maxChats = -1; + + this.metaData = new HashMap(); + + this.queues = new HashMap(); + + offerListeners = new ArrayList(); + invitationListeners = new ArrayList(); + queueUsersListeners = new ArrayList(); + + // 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) { + String oldVal = (String)this.metaData.get(key); + + if ((oldVal == null) || (!oldVal.equals(val))) { + metaData.put(key, 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) { + String oldVal = (String)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 null if the meta-data + * doesn't exist.. + */ + public String getMetaData(String key) { + return (String)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:

+ *

+ * 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:

+ *

+ * 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:

+ * + * @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 + *

+ * 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 getQueues() { + return Collections.unmodifiableMap((new HashMap(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 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 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 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 ChatNote 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 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.

+ * + * 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.

+ * + * The agent or user that accepted the offer MUST 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.

+ * + * 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.

+ * + * Once the invitee joins the support room the workgroup service will kick the inviter from the room.

+ * + * 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 XMPPConnection 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(XMPPConnection 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(XMPPConnection 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(XMPPConnection 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/source/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java b/source/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java new file mode 100644 index 000000000..16b324ae2 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/Offer.java b/source/org/jivesoftware/smackx/workgroup/agent/Offer.java new file mode 100644 index 000000000..51793c8e7 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/agent/Offer.java @@ -0,0 +1,222 @@ +/** + * $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.XMPPConnection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; + +import java.util.Date; +import java.util.Map; + +/** + * A class embodying the semantic agent chat offer; specific instances allow the acceptance or + * rejecting of the offer.
+ * + * @author Matt Tucker + * @author loki der quaeler + * @author Derek DeMoro + */ +public class Offer { + + private XMPPConnection connection; + private AgentSession session; + + private String sessionID; + private String userJID; + private String userID; + private String workgroupName; + private Date expiresDate; + private Map 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(XMPPConnection conn, AgentSession agentSession, String userID, + String userJID, String workgroupName, Date expiresDate, + String sessionID, Map 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 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 ""; + } + } + + /** + * 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 ""; + } + } + +} \ No newline at end of file diff --git a/source/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java b/source/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java new file mode 100644 index 000000000..56c1a67ba --- /dev/null +++ b/source/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.XMPPConnection; +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(XMPPConnection con, String workgroup, String createdRoomName) { + NotifyServicePacket packet = new NotifyServicePacket(workgroup, createdRoomName); + con.sendPacket(packet); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(""); + 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 ""; + } + } + + +} diff --git a/source/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java b/source/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java new file mode 100644 index 000000000..fb10550cf --- /dev/null +++ b/source/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 Offer. 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/source/org/jivesoftware/smackx/workgroup/agent/OfferContent.java b/source/org/jivesoftware/smackx/workgroup/agent/OfferContent.java new file mode 100644 index 000000000..a11ddc3d8 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/OfferListener.java b/source/org/jivesoftware/smackx/workgroup/agent/OfferListener.java new file mode 100644 index 000000000..5efde9919 --- /dev/null +++ b/source/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.
+ * + * @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/source/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java b/source/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java new file mode 100644 index 000000000..620a58c0f --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java @@ -0,0 +1,58 @@ +/** + * $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; + +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 users); +} \ No newline at end of file diff --git a/source/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java b/source/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java new file mode 100644 index 000000000..dab4d912f --- /dev/null +++ b/source/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.
+ * + * @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/source/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java b/source/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java new file mode 100644 index 000000000..07d5c5688 --- /dev/null +++ b/source/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.XMPPConnection; +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 XMPPConnection connection; + + public TranscriptManager(XMPPConnection 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/source/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java b/source/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java new file mode 100644 index 000000000..a262da954 --- /dev/null +++ b/source/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.XMPPConnection; +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 XMPPConnection connection; + + public TranscriptSearchManager(XMPPConnection 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/source/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java b/source/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java new file mode 100644 index 000000000..a3abbaa38 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/UserRequest.java b/source/org/jivesoftware/smackx/workgroup/agent/UserRequest.java new file mode 100644 index 000000000..ccaaaf37a --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java b/source/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java new file mode 100644 index 000000000..696d892a4 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java @@ -0,0 +1,222 @@ +/** + * $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.*; + +/** + * 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 users = Collections.EMPTY_SET; + + 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 getUsers() { + if (users == null) { + return Collections.EMPTY_SET.iterator(); + } + return Collections.unmodifiableSet(users).iterator(); + } + + void setUsers(Set 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 null. + * + * @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: + * + *

+ */ + 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/source/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java b/source/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java new file mode 100644 index 000000000..f2dc08e90 --- /dev/null +++ b/source/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(" "); + + 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/source/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java b/source/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java new file mode 100644 index 000000000..027c269ae --- /dev/null +++ b/source/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 agentChatSessions = new ArrayList(); + + 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 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("> "); + 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/source/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java b/source/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java new file mode 100644 index 000000000..5113cda1e --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java b/source/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java new file mode 100644 index 000000000..cb36fc723 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java @@ -0,0 +1,115 @@ +/** + * $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.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 map = new HashMap(); + + public void setMetadata(Map metadata){ + this.map = metadata; + } + + public Map getMetadata(){ + return map; + } + + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + buf.append("").append(getSessionID()).append(""); + buf.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 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/source/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java b/source/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java new file mode 100644 index 000000000..acf619687 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java b/source/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java new file mode 100644 index 000000000..87382807f --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.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.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 macros; + private List macroGroups; + + + // Define MacroGroup + private String title; + + public MacroGroup() { + macros = new ArrayList(); + macroGroups = new ArrayList(); + } + + public void addMacro(Macro macro) { + macros.add(macro); + } + + public void removeMacro(Macro macro) { + macros.remove(macro); + } + + public Macro getMacroByTitle(String title) { + Collection col = Collections.unmodifiableList(macros); + Iterator 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 col = Collections.unmodifiableList(macroGroups); + Iterator 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 getMacros() { + return macros; + } + + public void setMacros(List macros) { + this.macros = macros; + } + + public List getMacroGroups() { + return macroGroups; + } + + public void setMacroGroups(List macroGroups) { + this.macroGroups = macroGroups; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java b/source/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java new file mode 100644 index 000000000..509bd69c7 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java @@ -0,0 +1,144 @@ +/** + * $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 org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +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; + + private static ClassLoader cl; + + public static void setClassLoader(ClassLoader cloader) { + cl = cloader; + } + + + 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("true"); + } + // TODO: REMOVE XSTREAM +// if (getPersonalMacroGroup() != null) { +// final XStream xstream = new XStream(); +// xstream.alias("macro", Macro.class); +// xstream.alias("macrogroup", MacroGroup.class); +// +// if (cl != null) { +// xstream.setClassLoader(cl); +// } +// String persitedGroup = StringUtils.escapeForXML(xstream.toXML(getPersonalMacroGroup())); +// buf.append("").append(persitedGroup).append(""); +// } + buf.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(); + // TODO: REMOVE XSTREAM +// XStream xstream = new XStream(); +// if(cl != null){ +// xstream.setClassLoader(cl); +// } +// xstream.alias("macro", Macro.class); +// xstream.alias("macrogroup", MacroGroup.class); +// MacroGroup group = (MacroGroup)xstream.fromXML(macros); +// macroGroup.setRootGroup(group); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return macroGroup; + } + } +} \ No newline at end of file diff --git a/source/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java b/source/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java new file mode 100644 index 000000000..eff3c6c6d --- /dev/null +++ b/source/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("").append(getSessionID()).append(""); + + if (getNotes() != null) { + buf.append("").append(getNotes()).append(""); + } + buf.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/source/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java b/source/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java new file mode 100644 index 000000000..8b9d23073 --- /dev/null +++ b/source/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("").append(getJid()).append(""); + } + if (name != null) { + buf.append("").append(getName()).append(""); + } + buf.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/source/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java b/source/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java new file mode 100644 index 000000000..0878f878c --- /dev/null +++ b/source/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 currentChats = new ArrayList(); + 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 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("").append(maxChats).append(""); + } + if (!currentChats.isEmpty()) { + buf.append(""); + for (Iterator it = currentChats.iterator(); it.hasNext();) { + buf.append(((ChatInfo)it.next()).toXML()); + } + buf.append(""); + } + buf.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(""); + + 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/source/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java b/source/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java new file mode 100644 index 000000000..541a6cfab --- /dev/null +++ b/source/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 agents; + + public AgentStatusRequest() { + agents = new HashSet(); + } + + public int getAgentCount() { + return agents.size(); + } + + public Set 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 i=agents.iterator(); i.hasNext(); ) { + Item item = (Item) i.next(); + buf.append(""); + if (item.getName() != null) { + buf.append(""); + buf.append(item.getName()); + buf.append(""); + } + buf.append(""); + } + } + buf.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/source/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java b/source/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java new file mode 100644 index 000000000..b1578fdc6 --- /dev/null +++ b/source/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 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(); + } + + /** + * 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 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 getWorkgroups() { + return Collections.unmodifiableList(workgroups); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (Iterator it=workgroups.iterator(); it.hasNext();) { + String workgroupJID = (String) it.next(); + buf.append(""); + } + + buf.append(""); + + 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 workgroups = new ArrayList(); + + 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/source/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java b/source/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java new file mode 100644 index 000000000..620291c49 --- /dev/null +++ b/source/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: + * + * @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("").append(this.user).append(""); + } + else { + buf.append("/>"); + } + + return buf.toString(); + } +} \ No newline at end of file diff --git a/source/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java b/source/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java new file mode 100644 index 000000000..eb868b05e --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java @@ -0,0 +1,48 @@ +/** + * $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.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 metaData = MetaDataUtils.parseMetaData(parser); + + return new MetaData(metaData); + } +} \ No newline at end of file diff --git a/source/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java b/source/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java new file mode 100644 index 000000000..e9eb9a881 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java @@ -0,0 +1,107 @@ +/** + * $RCSfile: ,v $ + * $Revision: $ + * $Date: $ + * + * Copyright (C) 1999-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ +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(""); + } + buf.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/source/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java b/source/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java new file mode 100644 index 000000000..0f8086649 --- /dev/null +++ b/source/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 occupants; + + public OccupantsInfo(String roomID) { + this.roomID = roomID; + this.occupants = new HashSet(); + } + + public String getRoomID() { + return roomID; + } + + public int getOccupantsCount() { + return occupants.size(); + } + + public Set 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(""); + // Add the occupant jid + buf.append(""); + buf.append(occupant.getJID()); + buf.append(""); + // Add the occupant nickname + buf.append(""); + buf.append(occupant.getNickname()); + buf.append(""); + // Add the date when the occupant joined the room + buf.append(""); + buf.append(UTC_FORMAT.format(occupant.getJoined())); + buf.append(""); + buf.append(""); + } + } + buf.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/source/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java b/source/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java new file mode 100644 index 000000000..112a2ce71 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java @@ -0,0 +1,210 @@ +/** + * $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.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 metaData = new HashMap(); + + 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 metaData; + private String sessionID; + private OfferContent content; + + public OfferRequestPacket(String userJID, String userID, int timeout, Map 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, null 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 getMetaData() { + return this.metaData; + } + + public String getChildElementXML () { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + buf.append("").append(timeout).append(""); + + 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(""); + + return buf.toString(); + } + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java b/source/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java new file mode 100644 index 000000000..202824c21 --- /dev/null +++ b/source/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.
+ * + * @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(""); + if (reason != null) { + buf.append("").append(reason).append(""); + } + if (sessionID != null) { + buf.append(new SessionID(sessionID).toXML()); + } + if (userID != null) { + buf.append(new UserID(userID).toXML()); + } + buf.append(""); + return buf.toString(); + } + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java new file mode 100644 index 000000000..f5f27152d --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java @@ -0,0 +1,200 @@ +/** + * $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 SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + + /** + * The list of users in the queue. + */ + private Set users; + + /** + * Creates a new QueueDetails packet + */ + private QueueDetails() { + users = new HashSet(); + } + + /** + * 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 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 i=users.iterator(); i.hasNext(); ) { + QueueUser user = (QueueUser)i.next(); + int position = user.getQueuePosition(); + int timeRemaining = user.getEstimatedRemainingTime(); + Date timestamp = user.getQueueJoinTimestamp(); + + buf.append(""); + + if (position != -1) { + buf.append("").append(position).append(""); + } + + if (timeRemaining != -1) { + buf.append(""); + } + + if (timestamp != null) { + buf.append(""); + buf.append(DATE_FORMATTER.format(timestamp)); + buf.append(""); + } + + buf.append(""); + } + } + buf.append(""); + return buf.toString(); + } + + /** + * Provider class for QueueDetails packet extensions. + */ + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + 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 = DATE_FORMATTER.parse(parser.nextText()); + } + else if( parser.getName().equals( "waitTime" ) ) { + Date wait = DATE_FORMATTER.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/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java b/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java new file mode 100644 index 000000000..ef44e9e01 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java @@ -0,0 +1,158 @@ +/** + * $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 SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + + 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("").append(userCount).append(""); + } + if (oldestEntry != null) { + buf.append("").append(DATE_FORMATTER.format(oldestEntry)).append(""); + } + if (averageWaitTime != -1) { + buf.append(""); + } + if (status != null) { + buf.append("").append(status).append(""); + } + buf.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(); + + 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((DATE_FORMATTER.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/source/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java b/source/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java new file mode 100644 index 000000000..c326a570c --- /dev/null +++ b/source/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(""); + if (position != -1) { + buf.append("").append(position).append(""); + } + if (remainingTime != -1) { + buf.append(""); + } + buf.append(""); + 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/source/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/source/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java new file mode 100644 index 000000000..34555dec7 --- /dev/null +++ b/source/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(""); + if (invitee != null) { + buf.append("").append(invitee).append(""); + } + if (inviter != null) { + buf.append("").append(inviter).append(""); + } + if (reason != null) { + buf.append("").append(reason).append(""); + } + // Add packet extensions, if any are defined. + buf.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/source/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/source/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java new file mode 100644 index 000000000..d1e83e24f --- /dev/null +++ b/source/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(""); + if (invitee != null) { + buf.append("").append(invitee).append(""); + } + if (inviter != null) { + buf.append("").append(inviter).append(""); + } + if (reason != null) { + buf.append("").append(reason).append(""); + } + // Add packet extensions, if any are defined. + buf.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/source/org/jivesoftware/smackx/workgroup/packet/SessionID.java b/source/org/jivesoftware/smackx/workgroup/packet/SessionID.java new file mode 100644 index 000000000..bfd7cfd72 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/packet/Transcript.java b/source/org/jivesoftware/smackx/workgroup/packet/Transcript.java new file mode 100644 index 000000000..93d4ab890 --- /dev/null +++ b/source/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 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(); + } + + /** + * 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 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 getPackets() { + return Collections.unmodifiableList(packets); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (Iterator it=packets.iterator(); it.hasNext();) { + Packet packet = (Packet) it.next(); + buf.append(packet.toXML()); + } + + buf.append(""); + + return buf.toString(); + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java b/source/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java new file mode 100644 index 000000000..01ce83434 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java @@ -0,0 +1,65 @@ +/** + * $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.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 packets = new ArrayList(); + + 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/source/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java b/source/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java new file mode 100644 index 000000000..72693c494 --- /dev/null +++ b/source/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(" "); + + 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/source/org/jivesoftware/smackx/workgroup/packet/Transcripts.java b/source/org/jivesoftware/smackx/workgroup/packet/Transcripts.java new file mode 100644 index 000000000..66ddaad7e --- /dev/null +++ b/source/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 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(); + } + + /** + * 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 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 getSummaries() { + return Collections.unmodifiableList(summaries); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (TranscriptSummary transcriptSummary : summaries) { + buf.append(transcriptSummary.toXML()); + } + + buf.append(""); + + 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 agentDetails; + + public TranscriptSummary(String sessionID, Date joinTime, Date leftTime, List 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 getAgentDetails() { + return agentDetails; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + if (joinTime != null) { + buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); + } + if (leftTime != null) { + buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); + } + buf.append(""); + for (AgentDetail agentDetail : agentDetails) { + buf.append(agentDetail.toXML()); + } + buf.append(""); + + 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(""); + + if (agentJID != null) { + buf.append("").append(agentJID).append(""); + } + if (joinTime != null) { + buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); + } + if (leftTime != null) { + buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); + } + buf.append(""); + + return buf.toString(); + } + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java b/source/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java new file mode 100644 index 000000000..cb8f42979 --- /dev/null +++ b/source/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 summaries = new ArrayList(); + + 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 agents = new ArrayList(); + + 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 parseAgents(XmlPullParser parser) throws IOException, XmlPullParserException { + List agents = new ArrayList(); + 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/source/org/jivesoftware/smackx/workgroup/packet/UserID.java b/source/org/jivesoftware/smackx/workgroup/packet/UserID.java new file mode 100644 index 000000000..8bf458905 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java b/source/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java new file mode 100644 index 000000000..b0ea447a4 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java b/source/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java new file mode 100644 index 000000000..921134ace --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java b/source/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java new file mode 100644 index 000000000..60e452113 --- /dev/null +++ b/source/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 settings; + private String key; + private int type = -1; + + public ChatSettings() { + settings = new ArrayList(); + } + + 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 getSettings() { + return settings; + } + + public ChatSetting getChatSetting(String key) { + Collection col = getSettings(); + if (col != null) { + Iterator iter = col.iterator(); + while (iter.hasNext()) { + ChatSetting 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("> "); + 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/source/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java b/source/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java new file mode 100644 index 000000000..dcbd32bd3 --- /dev/null +++ b/source/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 map = new HashMap(); + + private String query; + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public Map getMap() { + return map; + } + + public void setMap(Map 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("" + getQuery() + ""); + } + buf.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/source/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java b/source/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java new file mode 100644 index 000000000..15136fd12 --- /dev/null +++ b/source/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("> "); + 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/source/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java b/source/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java new file mode 100644 index 000000000..4011b43d4 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java @@ -0,0 +1,108 @@ +/** + * $RCSfile$ + * $Revision: 38648 $ + * $Date: 2006-12-27 01:46:18 -0800 (Wed, 27 Dec 2006) $ + * + * Copyright (C) 1999-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. Use is + subject to license terms. + */ + +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("> "); + 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/source/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java b/source/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java new file mode 100644 index 000000000..66bec35b4 --- /dev/null +++ b/source/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("> "); + 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/source/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java b/source/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java new file mode 100644 index 000000000..8e405bbcb --- /dev/null +++ b/source/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("> "); + 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/source/org/jivesoftware/smackx/workgroup/user/QueueListener.java b/source/org/jivesoftware/smackx/workgroup/user/QueueListener.java new file mode 100644 index 000000000..fa3e6a6cf --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/workgroup/user/Workgroup.java b/source/org/jivesoftware/smackx/workgroup/user/Workgroup.java new file mode 100644 index 000000000..4db30db2a --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/user/Workgroup.java @@ -0,0 +1,938 @@ +/** + * $RCSfile$ + * $Revision: 38648 $ + * $Date: 2006-12-27 01:46:18 -0800 (Wed, 27 Dec 2006) $ + * + * Copyright (C) 2003-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +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.email.EmailIQ; +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.

+ *

+ * 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 XMPPConnection connection; + private boolean inQueue; + private List invitationListeners; + private List queueListeners; + private List siteInviteListeners; + + 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, XMPPConnection connection) { + // Login must have been done before passing in connection. + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must login to server before creating workgroup."); + } + + this.workgroupJID = workgroupJID; + this.connection = connection; + inQueue = false; + invitationListeners = new ArrayList(); + queueListeners = new ArrayList(); + siteInviteListeners = new ArrayList(); + + // Register as a queue listener for internal usage by this instance. + addQueueListener(new QueueListener() { + public void joinedQueue() { + inQueue = true; + } + + public void departedQueue() { + inQueue = false; + queuePosition = -1; + queueRemainingTime = -1; + } + + public void queuePositionUpdated(int currentPosition) { + queuePosition = currentPosition; + } + + public void queueWaitTimeUpdated(int secondsRemaining) { + queueRemainingTime = secondsRemaining; + } + }); + + /** + * 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(XMPPConnection 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:

    + *

    + *

  • The user is routed to an agent, which triggers a GroupChat invitation. + *
  • The user asks to leave the queue by calling the {@link #departQueue} method. + *
  • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
+ *

+ * 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.

+ *

+ * 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.

+ *

+ * 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:

    + *

    + *

  • The user is routed to an agent, which triggers a GroupChat invitation. + *
  • The user asks to leave the queue by calling the {@link #departQueue} method. + *
  • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
+ *

+ * 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.

+ *

+ * Some servers may be configured to require certain meta-data in order to + * join the queue.

+ *

+ * 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); + } + + /** + *

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:

    + *

    + *

  • The user is routed to an agent, which triggers a GroupChat invitation. + *
  • The user asks to leave the queue by calling the {@link #departQueue} method. + *
  • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
+ *

+ * 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.

+ *

+ * Some servers may be configured to require certain meta-data in order to + * join the queue.

+ *

+ * 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 null 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(); + } + + /** + *

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:

    + *

    + *

  • The user is routed to an agent, which triggers a GroupChat invitation. + *
  • The user asks to leave the queue by calling the {@link #departQueue} method. + *
  • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
+ *

+ * 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.

+ *

+ * Some servers may be configured to require certain meta-data in order to + * join the queue.

+ *

+ * 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 null 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 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 iter = metadata.keySet().iterator(); + while (iter.hasNext()) { + String name = (String)iter.next(); + String value = (String)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.

+ *

+ * 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 i = invitationListeners.iterator(); i.hasNext();) { + WorkgroupInvitationListener listener = (WorkgroupInvitationListener)i.next(); + listener.invitationReceived(invitation); + } + } + } + + private void fireQueueJoinedEvent() { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.joinedQueue(); + } + } + } + + private void fireQueueDepartedEvent() { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.departedQueue(); + } + } + } + + private void fireQueuePositionEvent(int currentPosition) { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.queuePositionUpdated(currentPosition); + } + } + } + + private void fireQueueTimeEvent(int secondsRemaining) { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.queueWaitTimeUpdated(secondsRemaining); + } + } + } + + // PacketListener Implementation. + + private void handlePacket(Packet packet) { + if (packet instanceof Message) { + Message msg = (Message)packet; + // Check to see if the user left the queue. + PacketExtension pe = msg.getExtension("depart-queue", "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 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(""); + buf.append(""); + // 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(""); + + 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; + } + } + + /** + * Send an email from the workgroup. + * + * @param to the to address of the the user to send to. + * @param from who the email is from. + * @param subject the subject of the email. + * @param message the body of the email. + * @param sendAsHTML true if the message should be sent as html, otherwise specify false for plain text. + * @return true if the email was sent successfully, otherwise false. + * @throws XMPPException if an error occurs while sending the email. + */ + public boolean sendMail(String to, String from, String subject, String message, boolean sendAsHTML) throws XMPPException { + EmailIQ request = new EmailIQ(); + request.setToAddress(to); + request.setFromAddress(from); + request.setSubject(subject); + request.setMessage(message); + request.setHtml(sendAsHTML); + + request.setType(IQ.Type.SET); + request.setTo(workgroupJID); + + 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."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return true; + } + + /** + * Send an email from the workgroup. + * + * @param to the to address of the the user to send to. + * @param sessionID the sessionID of the chat. + * @return true if the email was sent successfully, otherwise false. + * @throws XMPPException if an error occurs while sending the email. + */ + public boolean sendTranscript(String to, String sessionID) throws XMPPException { + EmailIQ request = new EmailIQ(); + request.setToAddress(to); + request.setSessionID(sessionID); + + request.setType(IQ.Type.SET); + request.setTo(workgroupJID); + + 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."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return true; + } + + /** + * 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 { + XMPPConnection 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/source/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java b/source/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java new file mode 100644 index 000000000..ea928fe61 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java @@ -0,0 +1,126 @@ +/** + * $RCSfile$ + * $Revision: 19406 $ + * $Date: 2005-07-28 18:12:09 -0700 (Thu, 28 Jul 2005) $ + * + * Copyright (C) 2003-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +package org.jivesoftware.smackx.workgroup.util; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * 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.

+ * + * 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 triplets; + + protected transient boolean hasFinishedDispatching; + protected transient boolean isRunning; + + public ListenerEventDispatcher () { + super(); + + this.triplets = new ArrayList(); + + 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.
+ * + * 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.
+ * + * 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.
+ * + * @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 li = null; + + this.isRunning = true; + + li = this.triplets.listIterator(); + while (li.hasNext()) { + TripletContainer tc = (TripletContainer)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/source/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java b/source/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java new file mode 100644 index 000000000..e7d5052a9 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java @@ -0,0 +1,106 @@ +/** + * $RCSfile$ + * $Revision: 38648 $ + * $Date: 2006-12-27 01:46:18 -0800 (Wed, 27 Dec 2006) $ + * + * Copyright (C) 2003-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +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 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 metaData = new Hashtable(); + + 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 values = (List)metaData.get(name); + values.add(value); + } + else { + List values = new ArrayList(); + values.add(value); + metaData.put(name, values); + } + + eventType = parser.nextTag(); + } + + return metaData; + } + + return Collections.EMPTY_MAP; + } + + /** + * Serializes a Map of String name/value pairs into the meta-data XML format. + * + * @param metaData the Map of meta-data. + * @return the meta-data values in XML form. + */ + public static String serializeMetaData(Map metaData) { + StringBuilder buf = new StringBuilder(); + if (metaData != null && metaData.size() > 0) { + buf.append(""); + for (Iterator i = metaData.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = metaData.get(key); + if (value instanceof List) { + List values = (List)metaData.get(key); + for (Iterator it = values.iterator(); it.hasNext();) { + String v = (String)it.next(); + buf.append(""); + buf.append(StringUtils.escapeForXML(v)); + buf.append(""); + } + } + else if (value instanceof String) { + buf.append(""); + buf.append(StringUtils.escapeForXML((String)value)); + buf.append(""); + } + } + buf.append(""); + } + return buf.toString(); + } +} diff --git a/source/org/jivesoftware/smackx/workgroup/util/ModelUtil.java b/source/org/jivesoftware/smackx/workgroup/util/ModelUtil.java new file mode 100644 index 000000000..ae46a5e44 --- /dev/null +++ b/source/org/jivesoftware/smackx/workgroup/util/ModelUtil.java @@ -0,0 +1,318 @@ +/** + * $RCSfile$ + * $Revision: 38648 $ + * $Date: 2006-12-27 01:46:18 -0800 (Wed, 27 Dec 2006) $ + * + * Copyright (C) 2004-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +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 null The result of + * this method is determined as follows: + *

    + *
  1. If o1 and o2 are the same object + * according to the == operator, return + * true. + *
  2. Otherwise, if either o1 or o2 is + * null, return false. + *
  3. Otherwise, return o1.equals(o2). + *
+ *

+ * This method produces the exact logically inverted result as the + * {@link #areDifferent(Object, Object)} method.

+ *

+ * For array types, one of the equals 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 equals 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 null The result of + * this method is determined as follows: + *

    + *
  1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return true. + *
  2. Otherwise, return false. + *
+ *

+ */ + 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 null. The result + * returned by this method is determined as follows: + *

    + *
  1. If o1 and o2 are the same object + * according to the == operator, return + * false. + *
  2. Otherwise, if either o1 or o2 is + * null, return true. + *
  3. Otherwise, return !o1.equals(o2). + *
+ *

+ * This method produces the exact logically inverted result as the + * {@link #areEqual(Object, Object)} method.

+ *

+ * For array types, one of the equals 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 equals 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 null The result of + * this method is determined as follows: + *

    + *
  1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return false. + *
  2. Otherwise, return true. + *
+ *

+ * This method produces the exact logically inverted result as the + * {@link #areBooleansEqual(Boolean, Boolean)} method.

+ */ + public static final boolean areBooleansDifferent(Boolean b1, Boolean b2) { + return !areBooleansEqual(b1, b2); + } + + + /** + * Returns true if the specified array is not null + * and contains a non-null element. Returns false + * 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 true if the specified {@link String} is not + * null 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 null if the specified string is empty or + * null. Otherwise the string itself is returned. + */ + public static final String nullifyIfEmpty(String s) { + return ModelUtil.hasLength(s) ? s : null; + } + + /** + * Returns null if the specified object is null + * or if its toString() representation is empty. + * Otherwise, the toString() 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; + Date currentTime = new Date(); + long numDays = diff / MS_IN_A_DAY; + 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; + long numMilliseconds = diff; + + 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 List iteratorAsList(Iterator i) { + ArrayList list = new ArrayList(10); + while (i.hasNext()) { + list.add(i.next()); + } + return list; + } + + /** + * Creates an Iterator that is the reverse of a ListIterator. + */ + public static Iterator reverseListIterator(ListIterator i) { + return new ReverseListIterator(i); + } +} + +/** + * An Iterator that is the reverse of a ListIterator. + */ +class ReverseListIterator implements Iterator { + private ListIterator _i; + + ReverseListIterator(ListIterator i) { + _i = i; + while (_i.hasNext()) + _i.next(); + } + + public boolean hasNext() { + return _i.hasPrevious(); + } + + public Object next() { + return _i.previous(); + } + + public void remove() { + _i.remove(); + } +} + + + + + + + + + + +