Workgroup.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.workgroup.user;

  18. import java.util.concurrent.CopyOnWriteArraySet;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Map;

  22. import org.jivesoftware.smack.PacketCollector;
  23. import org.jivesoftware.smack.StanzaListener;
  24. import org.jivesoftware.smack.SmackException;
  25. import org.jivesoftware.smack.SmackException.NoResponseException;
  26. import org.jivesoftware.smack.SmackException.NotConnectedException;
  27. import org.jivesoftware.smack.XMPPConnection;
  28. import org.jivesoftware.smack.XMPPException;
  29. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  30. import org.jivesoftware.smack.filter.AndFilter;
  31. import org.jivesoftware.smack.filter.FromMatchesFilter;
  32. import org.jivesoftware.smack.filter.StanzaFilter;
  33. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  34. import org.jivesoftware.smack.packet.IQ;
  35. import org.jivesoftware.smack.packet.Message;
  36. import org.jivesoftware.smack.packet.Stanza;
  37. import org.jivesoftware.smack.packet.ExtensionElement;
  38. import org.jivesoftware.smack.packet.Presence;
  39. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  40. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  41. import org.jivesoftware.smackx.muc.MultiUserChatManager;
  42. import org.jivesoftware.smackx.muc.packet.MUCUser;
  43. import org.jivesoftware.smackx.workgroup.MetaData;
  44. import org.jivesoftware.smackx.workgroup.WorkgroupInvitation;
  45. import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener;
  46. import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm;
  47. import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket;
  48. import org.jivesoftware.smackx.workgroup.packet.QueueUpdate;
  49. import org.jivesoftware.smackx.workgroup.packet.SessionID;
  50. import org.jivesoftware.smackx.workgroup.packet.UserID;
  51. import org.jivesoftware.smackx.workgroup.settings.ChatSetting;
  52. import org.jivesoftware.smackx.workgroup.settings.ChatSettings;
  53. import org.jivesoftware.smackx.workgroup.settings.OfflineSettings;
  54. import org.jivesoftware.smackx.workgroup.settings.SoundSettings;
  55. import org.jivesoftware.smackx.workgroup.settings.WorkgroupProperties;
  56. import org.jivesoftware.smackx.xdata.Form;
  57. import org.jivesoftware.smackx.xdata.FormField;
  58. import org.jivesoftware.smackx.xdata.packet.DataForm;
  59. import org.jxmpp.jid.DomainBareJid;
  60. import org.jxmpp.jid.Jid;

  61. /**
  62.  * Provides workgroup services for users. Users can join the workgroup queue, depart the
  63.  * queue, find status information about their placement in the queue, and register to
  64.  * be notified when they are routed to an agent.<p>
  65.  * <p/>
  66.  * This class only provides a users perspective into a workgroup and is not intended
  67.  * for use by agents.
  68.  *
  69.  * @author Matt Tucker
  70.  * @author Derek DeMoro
  71.  */
  72. public class Workgroup {

  73.     private Jid workgroupJID;
  74.     private XMPPConnection connection;
  75.     private boolean inQueue;
  76.     private CopyOnWriteArraySet<WorkgroupInvitationListener> invitationListeners;
  77.     private CopyOnWriteArraySet<QueueListener> queueListeners;

  78.     private int queuePosition = -1;
  79.     private int queueRemainingTime = -1;

  80.     /**
  81.      * Creates a new workgroup instance using the specified workgroup JID
  82.      * (eg support@workgroup.example.com) and XMPP connection. The connection must have
  83.      * undergone a successful login before being used to construct an instance of
  84.      * this class.
  85.      *
  86.      * @param workgroupJID the JID of the workgroup.
  87.      * @param connection   an XMPP connection which must have already undergone a
  88.      *                     successful login.
  89.      */
  90.     public Workgroup(Jid workgroupJID, XMPPConnection connection) {
  91.         // Login must have been done before passing in connection.
  92.         if (!connection.isAuthenticated()) {
  93.             throw new IllegalStateException("Must login to server before creating workgroup.");
  94.         }

  95.         this.workgroupJID = workgroupJID;
  96.         this.connection = connection;
  97.         inQueue = false;
  98.         invitationListeners = new CopyOnWriteArraySet<>();
  99.         queueListeners = new CopyOnWriteArraySet<>();

  100.         // Register as a queue listener for internal usage by this instance.
  101.         addQueueListener(new QueueListener() {
  102.             public void joinedQueue() {
  103.                 inQueue = true;
  104.             }

  105.             public void departedQueue() {
  106.                 inQueue = false;
  107.                 queuePosition = -1;
  108.                 queueRemainingTime = -1;
  109.             }

  110.             public void queuePositionUpdated(int currentPosition) {
  111.                 queuePosition = currentPosition;
  112.             }

  113.             public void queueWaitTimeUpdated(int secondsRemaining) {
  114.                 queueRemainingTime = secondsRemaining;
  115.             }
  116.         });

  117.         /**
  118.          * Internal handling of an invitation.Recieving an invitation removes the user from the queue.
  119.          */
  120.         MultiUserChatManager.getInstanceFor(connection).addInvitationListener(
  121.                 new org.jivesoftware.smackx.muc.InvitationListener() {
  122.                     @Override
  123.                     public void invitationReceived(XMPPConnection conn, org.jivesoftware.smackx.muc.MultiUserChat room, String inviter,
  124.                                                    String reason, String password, Message message) {
  125.                         inQueue = false;
  126.                         queuePosition = -1;
  127.                         queueRemainingTime = -1;
  128.                     }
  129.                 });

  130.         // Register a packet listener for all the messages sent to this client.
  131.         StanzaFilter typeFilter = new StanzaTypeFilter(Message.class);

  132.         connection.addAsyncStanzaListener(new StanzaListener() {
  133.             public void processPacket(Stanza packet) {
  134.                 handlePacket(packet);
  135.             }
  136.         }, typeFilter);
  137.     }

  138.     /**
  139.      * Returns the name of this workgroup (eg support@example.com).
  140.      *
  141.      * @return the name of the workgroup.
  142.      */
  143.     public Jid getWorkgroupJID() {
  144.         return workgroupJID;
  145.     }

  146.     /**
  147.      * Returns true if the user is currently waiting in the workgroup queue.
  148.      *
  149.      * @return true if currently waiting in the queue.
  150.      */
  151.     public boolean isInQueue() {
  152.         return inQueue;
  153.     }

  154.     /**
  155.      * Returns true if the workgroup is available for receiving new requests. The workgroup will be
  156.      * available only when agents are available for this workgroup.
  157.      *
  158.      * @return true if the workgroup is available for receiving new requests.
  159.      * @throws XMPPErrorException
  160.      * @throws NoResponseException
  161.      * @throws NotConnectedException
  162.      * @throws InterruptedException
  163.      */
  164.     public boolean isAvailable() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  165.         Presence directedPresence = new Presence(Presence.Type.available);
  166.         directedPresence.setTo(workgroupJID);
  167.         StanzaFilter typeFilter = new StanzaTypeFilter(Presence.class);
  168.         StanzaFilter fromFilter = FromMatchesFilter.create(workgroupJID);
  169.         PacketCollector collector = connection.createPacketCollectorAndSend(new AndFilter(fromFilter,
  170.                 typeFilter), directedPresence);

  171.         Presence response = (Presence)collector.nextResultOrThrow();
  172.         return Presence.Type.available == response.getType();
  173.     }

  174.     /**
  175.      * Returns the users current position in the workgroup queue. A value of 0 means
  176.      * the user is next in line to be routed; therefore, if the queue position
  177.      * is being displayed to the end user it is usually a good idea to add 1 to
  178.      * the value this method returns before display. If the user is not currently
  179.      * waiting in the workgroup, or no queue position information is available, -1
  180.      * will be returned.
  181.      *
  182.      * @return the user's current position in the workgroup queue, or -1 if the
  183.      *         position isn't available or if the user isn't in the queue.
  184.      */
  185.     public int getQueuePosition() {
  186.         return queuePosition;
  187.     }

  188.     /**
  189.      * Returns the estimated time (in seconds) that the user has to left wait in
  190.      * the workgroup queue before being routed. If the user is not currently waiting
  191.      * int he workgroup, or no queue time information is available, -1 will be
  192.      * returned.
  193.      *
  194.      * @return the estimated time remaining (in seconds) that the user has to
  195.      *         wait inthe workgroupu queue, or -1 if time information isn't available
  196.      *         or if the user isn't int the queue.
  197.      */
  198.     public int getQueueRemainingTime() {
  199.         return queueRemainingTime;
  200.     }

  201.     /**
  202.      * Joins the workgroup queue to wait to be routed to an agent. After joining
  203.      * the queue, queue status events will be sent to indicate the user's position and
  204.      * estimated time left in the queue. Once joining the queue, there are three ways
  205.      * the user can leave the queue: <ul>
  206.      * <p/>
  207.      * <li>The user is routed to an agent, which triggers a GroupChat invitation.
  208.      * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
  209.      * <li>A server error occurs, or an administrator explicitly removes the user
  210.      * from the queue.
  211.      * </ul>
  212.      * <p/>
  213.      * A user cannot request to join the queue again if already in the queue. Therefore,
  214.      * this method will throw an IllegalStateException if the user is already in the queue.<p>
  215.      * <p/>
  216.      * Some servers may be configured to require certain meta-data in order to
  217.      * join the queue. In that case, the {@link #joinQueue(Form)} method should be
  218.      * used instead of this method so that meta-data may be passed in.<p>
  219.      * <p/>
  220.      * The server tracks the conversations that a user has with agents over time. By
  221.      * default, that tracking is done using the user's JID. However, this is not always
  222.      * possible. For example, when the user is logged in anonymously using a web client.
  223.      * In that case the user ID might be a randomly generated value put into a persistent
  224.      * cookie or a username obtained via the session. A userID can be explicitly
  225.      * passed in by using the {@link #joinQueue(Form, Jid)} method. When specified,
  226.      * that userID will be used instead of the user's JID to track conversations. The
  227.      * server will ignore a manually specified userID if the user's connection to the server
  228.      * is not anonymous.
  229.      *
  230.      * @throws XMPPException if an error occured joining the queue. An error may indicate
  231.      *                       that a connection failure occured or that the server explicitly rejected the
  232.      *                       request to join the queue.
  233.      * @throws SmackException
  234.      * @throws InterruptedException
  235.      */
  236.     public void joinQueue() throws XMPPException, SmackException, InterruptedException {
  237.         joinQueue(null);
  238.     }

  239.     /**
  240.      * Joins the workgroup queue to wait to be routed to an agent. After joining
  241.      * the queue, queue status events will be sent to indicate the user's position and
  242.      * estimated time left in the queue. Once joining the queue, there are three ways
  243.      * the user can leave the queue: <ul>
  244.      * <p/>
  245.      * <li>The user is routed to an agent, which triggers a GroupChat invitation.
  246.      * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
  247.      * <li>A server error occurs, or an administrator explicitly removes the user
  248.      * from the queue.
  249.      * </ul>
  250.      * <p/>
  251.      * A user cannot request to join the queue again if already in the queue. Therefore,
  252.      * this method will throw an IllegalStateException if the user is already in the queue.<p>
  253.      * <p/>
  254.      * Some servers may be configured to require certain meta-data in order to
  255.      * join the queue.<p>
  256.      * <p/>
  257.      * The server tracks the conversations that a user has with agents over time. By
  258.      * default, that tracking is done using the user's JID. However, this is not always
  259.      * possible. For example, when the user is logged in anonymously using a web client.
  260.      * In that case the user ID might be a randomly generated value put into a persistent
  261.      * cookie or a username obtained via the session. A userID can be explicitly
  262.      * passed in by using the {@link #joinQueue(Form, Jid)} method. When specified,
  263.      * that userID will be used instead of the user's JID to track conversations. The
  264.      * server will ignore a manually specified userID if the user's connection to the server
  265.      * is not anonymous.
  266.      *
  267.      * @param answerForm the completed form the send for the join request.
  268.      * @throws XMPPException if an error occured joining the queue. An error may indicate
  269.      *                       that a connection failure occured or that the server explicitly rejected the
  270.      *                       request to join the queue.
  271.      * @throws SmackException
  272.      * @throws InterruptedException
  273.      */
  274.     public void joinQueue(Form answerForm) throws XMPPException, SmackException, InterruptedException {
  275.         joinQueue(answerForm, null);
  276.     }

  277.     /**
  278.      * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
  279.      * the queue, queue status events will be sent to indicate the user's position and
  280.      * estimated time left in the queue. Once joining the queue, there are three ways
  281.      * the user can leave the queue: <ul>
  282.      * <p/>
  283.      * <li>The user is routed to an agent, which triggers a GroupChat invitation.
  284.      * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
  285.      * <li>A server error occurs, or an administrator explicitly removes the user
  286.      * from the queue.
  287.      * </ul>
  288.      * <p/>
  289.      * A user cannot request to join the queue again if already in the queue. Therefore,
  290.      * this method will throw an IllegalStateException if the user is already in the queue.<p>
  291.      * <p/>
  292.      * Some servers may be configured to require certain meta-data in order to
  293.      * join the queue.<p>
  294.      * <p/>
  295.      * The server tracks the conversations that a user has with agents over time. By
  296.      * default, that tracking is done using the user's JID. However, this is not always
  297.      * possible. For example, when the user is logged in anonymously using a web client.
  298.      * In that case the user ID might be a randomly generated value put into a persistent
  299.      * cookie or a username obtained via the session. When specified, that userID will
  300.      * be used instead of the user's JID to track conversations. The server will ignore a
  301.      * manually specified userID if the user's connection to the server is not anonymous.
  302.      *
  303.      * @param answerForm the completed form associated with the join reqest.
  304.      * @param userID     String that represents the ID of the user when using anonymous sessions
  305.      *                   or <tt>null</tt> if a userID should not be used.
  306.      * @throws XMPPErrorException if an error occured joining the queue. An error may indicate
  307.      *                       that a connection failure occured or that the server explicitly rejected the
  308.      *                       request to join the queue.
  309.      * @throws NoResponseException
  310.      * @throws NotConnectedException
  311.      * @throws InterruptedException
  312.      */
  313.     public void joinQueue(Form answerForm, Jid userID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  314.         // If already in the queue ignore the join request.
  315.         if (inQueue) {
  316.             throw new IllegalStateException("Already in queue " + workgroupJID);
  317.         }

  318.         JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID);

  319.         connection.createPacketCollectorAndSend(joinPacket).nextResultOrThrow();
  320.         // Notify listeners that we've joined the queue.
  321.         fireQueueJoinedEvent();
  322.     }

  323.     /**
  324.      * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
  325.      * the queue, queue status events will be sent to indicate the user's position and
  326.      * estimated time left in the queue. Once joining the queue, there are three ways
  327.      * the user can leave the queue: <ul>
  328.      * <p/>
  329.      * <li>The user is routed to an agent, which triggers a GroupChat invitation.
  330.      * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
  331.      * <li>A server error occurs, or an administrator explicitly removes the user
  332.      * from the queue.
  333.      * </ul>
  334.      * <p/>
  335.      * A user cannot request to join the queue again if already in the queue. Therefore,
  336.      * this method will throw an IllegalStateException if the user is already in the queue.<p>
  337.      * <p/>
  338.      * Some servers may be configured to require certain meta-data in order to
  339.      * join the queue.<p>
  340.      * <p/>
  341.      * The server tracks the conversations that a user has with agents over time. By
  342.      * default, that tracking is done using the user's JID. However, this is not always
  343.      * possible. For example, when the user is logged in anonymously using a web client.
  344.      * In that case the user ID might be a randomly generated value put into a persistent
  345.      * cookie or a username obtained via the session. When specified, that userID will
  346.      * be used instead of the user's JID to track conversations. The server will ignore a
  347.      * manually specified userID if the user's connection to the server is not anonymous.
  348.      *
  349.      * @param metadata metadata to create a dataform from.
  350.      * @param userID   String that represents the ID of the user when using anonymous sessions
  351.      *                 or <tt>null</tt> if a userID should not be used.
  352.      * @throws XMPPException if an error occured joining the queue. An error may indicate
  353.      *                       that a connection failure occured or that the server explicitly rejected the
  354.      *                       request to join the queue.
  355.      * @throws SmackException
  356.      * @throws InterruptedException
  357.      */
  358.     public void joinQueue(Map<String,Object> metadata, Jid userID) throws XMPPException, SmackException, InterruptedException {
  359.         // If already in the queue ignore the join request.
  360.         if (inQueue) {
  361.             throw new IllegalStateException("Already in queue " + workgroupJID);
  362.         }

  363.         // Build dataform from metadata
  364.         Form form = new Form(DataForm.Type.submit);
  365.         Iterator<String> iter = metadata.keySet().iterator();
  366.         while (iter.hasNext()) {
  367.             String name = iter.next();
  368.             String value = metadata.get(name).toString();

  369.             FormField field = new FormField(name);
  370.             field.setType(FormField.Type.text_single);
  371.             form.addField(field);
  372.             form.setAnswer(name, value);
  373.         }
  374.         joinQueue(form, userID);
  375.     }

  376.     /**
  377.      * Departs the workgroup queue. If the user is not currently in the queue, this
  378.      * method will do nothing.<p>
  379.      * <p/>
  380.      * Normally, the user would not manually leave the queue. However, they may wish to
  381.      * under certain circumstances -- for example, if they no longer wish to be routed
  382.      * to an agent because they've been waiting too long.
  383.      *
  384.      * @throws XMPPErrorException if an error occured trying to send the depart queue
  385.      *                       request to the server.
  386.      * @throws NoResponseException
  387.      * @throws NotConnectedException
  388.      * @throws InterruptedException
  389.      */
  390.     public void departQueue() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  391.         // If not in the queue ignore the depart request.
  392.         if (!inQueue) {
  393.             return;
  394.         }

  395.         DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
  396.         connection.createPacketCollectorAndSend(departPacket).nextResultOrThrow();

  397.         // Notify listeners that we're no longer in the queue.
  398.         fireQueueDepartedEvent();
  399.     }

  400.     /**
  401.      * Adds a queue listener that will be notified of queue events for the user
  402.      * that created this Workgroup instance.
  403.      *
  404.      * @param queueListener the queue listener.
  405.      */
  406.     public void addQueueListener(QueueListener queueListener) {
  407.         queueListeners.add(queueListener);
  408.     }

  409.     /**
  410.      * Removes a queue listener.
  411.      *
  412.      * @param queueListener the queue listener.
  413.      */
  414.     public void removeQueueListener(QueueListener queueListener) {
  415.         queueListeners.remove(queueListener);
  416.     }

  417.     /**
  418.      * Adds an invitation listener that will be notified of groupchat invitations
  419.      * from the workgroup for the the user that created this Workgroup instance.
  420.      *
  421.      * @param invitationListener the invitation listener.
  422.      */
  423.     public void addInvitationListener(WorkgroupInvitationListener invitationListener) {
  424.         invitationListeners.add(invitationListener);
  425.     }

  426.     /**
  427.      * Removes an invitation listener.
  428.      *
  429.      * @param invitationListener the invitation listener.
  430.      */
  431.     public void removeQueueListener(WorkgroupInvitationListener invitationListener) {
  432.         invitationListeners.remove(invitationListener);
  433.     }

  434.     private void fireInvitationEvent(WorkgroupInvitation invitation) {
  435.         for (WorkgroupInvitationListener listener : invitationListeners ){
  436.             listener.invitationReceived(invitation);
  437.         }
  438.     }

  439.     private void fireQueueJoinedEvent() {
  440.         for (QueueListener listener : queueListeners){
  441.             listener.joinedQueue();
  442.         }
  443.     }

  444.     private void fireQueueDepartedEvent() {
  445.         for (QueueListener listener : queueListeners) {
  446.             listener.departedQueue();
  447.         }
  448.     }

  449.     private void fireQueuePositionEvent(int currentPosition) {
  450.         for (QueueListener listener : queueListeners) {
  451.             listener.queuePositionUpdated(currentPosition);
  452.         }
  453.     }

  454.     private void fireQueueTimeEvent(int secondsRemaining) {
  455.         for (QueueListener listener : queueListeners) {
  456.             listener.queueWaitTimeUpdated(secondsRemaining);
  457.         }
  458.     }

  459.     // PacketListener Implementation.

  460.     private void handlePacket(Stanza packet) {
  461.         if (packet instanceof Message) {
  462.             Message msg = (Message)packet;
  463.             // Check to see if the user left the queue.
  464.             ExtensionElement pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup");
  465.             ExtensionElement queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup");

  466.             if (pe != null) {
  467.                 fireQueueDepartedEvent();
  468.             }
  469.             else if (queueStatus != null) {
  470.                 QueueUpdate queueUpdate = (QueueUpdate)queueStatus;
  471.                 if (queueUpdate.getPosition() != -1) {
  472.                     fireQueuePositionEvent(queueUpdate.getPosition());
  473.                 }
  474.                 if (queueUpdate.getRemaingTime() != -1) {
  475.                     fireQueueTimeEvent(queueUpdate.getRemaingTime());
  476.                 }
  477.             }

  478.             else {
  479.                 // Check if a room invitation was sent and if the sender is the workgroup
  480.                 MUCUser mucUser = (MUCUser)msg.getExtension("x", "http://jabber.org/protocol/muc#user");
  481.                 MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null;
  482.                 if (invite != null && workgroupJID.equals(invite.getFrom())) {
  483.                     String sessionID = null;
  484.                     Map<String, List<String>> metaData = null;

  485.                     pe = msg.getExtension(SessionID.ELEMENT_NAME,
  486.                             SessionID.NAMESPACE);
  487.                     if (pe != null) {
  488.                         sessionID = ((SessionID)pe).getSessionID();
  489.                     }

  490.                     pe = msg.getExtension(MetaData.ELEMENT_NAME,
  491.                             MetaData.NAMESPACE);
  492.                     if (pe != null) {
  493.                         metaData = ((MetaData)pe).getMetaData();
  494.                     }

  495.                     WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(),
  496.                             workgroupJID, sessionID, msg.getBody(),
  497.                             msg.getFrom(), metaData);

  498.                     fireInvitationEvent(inv);
  499.                 }
  500.             }
  501.         }
  502.     }

  503.     /**
  504.      * IQ packet to request joining the workgroup queue.
  505.      */
  506.     private class JoinQueuePacket extends IQ {

  507.         private Jid userID;
  508.         private DataForm form;

  509.         public JoinQueuePacket(Jid workgroup, Form answerForm, Jid userID) {
  510.             super("join-queue", "http://jabber.org/protocol/workgroup");
  511.             this.userID = userID;

  512.             setTo(workgroup);
  513.             setType(IQ.Type.set);

  514.             form = answerForm.getDataFormToSend();
  515.             addExtension(form);
  516.         }

  517.         @Override
  518.         protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
  519.             buf.rightAngleBracket();
  520.             buf.append("<queue-notifications/>");
  521.             // Add the user unique identification if the session is anonymous
  522.             if (connection.isAnonymous()) {
  523.                 buf.append(new UserID(userID).toXML());
  524.             }

  525.             // Append data form text
  526.             buf.append(form.toXML());

  527.             return buf;
  528.         }
  529.     }

  530.     /**
  531.      * Returns a single chat setting based on it's identified key.
  532.      *
  533.      * @param key the key to find.
  534.      * @return the ChatSetting if found, otherwise false.
  535.      * @throws XMPPException if an error occurs while getting information from the server.
  536.      * @throws SmackException
  537.      * @throws InterruptedException
  538.      */
  539.     public ChatSetting getChatSetting(String key) throws XMPPException, SmackException, InterruptedException {
  540.         ChatSettings chatSettings = getChatSettings(key, -1);
  541.         return chatSettings.getFirstEntry();
  542.     }

  543.     /**
  544.      * Returns ChatSettings based on type.
  545.      *
  546.      * @param type the type of ChatSettings to return.
  547.      * @return the ChatSettings of given type, otherwise null.
  548.      * @throws XMPPException if an error occurs while getting information from the server.
  549.      * @throws SmackException
  550.      * @throws InterruptedException
  551.      */
  552.     public ChatSettings getChatSettings(int type) throws XMPPException, SmackException, InterruptedException {
  553.         return getChatSettings(null, type);
  554.     }

  555.     /**
  556.      * Returns all ChatSettings.
  557.      *
  558.      * @return all ChatSettings of a given workgroup.
  559.      * @throws XMPPException if an error occurs while getting information from the server.
  560.      * @throws SmackException
  561.      * @throws InterruptedException
  562.      */
  563.     public ChatSettings getChatSettings() throws XMPPException, SmackException, InterruptedException {
  564.         return getChatSettings(null, -1);
  565.     }


  566.     /**
  567.      * Asks the workgroup for it's Chat Settings.
  568.      *
  569.      * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null.
  570.      * @throws NoResponseException
  571.      * @throws XMPPErrorException if an error occurs while getting information from the server.
  572.      * @throws NotConnectedException
  573.      * @throws InterruptedException
  574.      */
  575.     private ChatSettings getChatSettings(String key, int type) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  576.         ChatSettings request = new ChatSettings();
  577.         if (key != null) {
  578.             request.setKey(key);
  579.         }
  580.         if (type != -1) {
  581.             request.setType(type);
  582.         }
  583.         request.setType(IQ.Type.get);
  584.         request.setTo(workgroupJID);

  585.         ChatSettings response = (ChatSettings) connection.createPacketCollectorAndSend(request).nextResultOrThrow();

  586.         return response;
  587.     }

  588.     /**
  589.      * The workgroup service may be configured to send email. This queries the Workgroup Service
  590.      * to see if the email service has been configured and is available.
  591.      *
  592.      * @return true if the email service is available, otherwise return false.
  593.      * @throws SmackException
  594.      * @throws InterruptedException
  595.      */
  596.     public boolean isEmailAvailable() throws SmackException, InterruptedException {
  597.         ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);

  598.         try {
  599.             DomainBareJid workgroupService = workgroupJID.asDomainBareJid();
  600.             DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService);
  601.             return infoResult.containsFeature("jive:email:provider");
  602.         }
  603.         catch (XMPPException e) {
  604.             return false;
  605.         }
  606.     }

  607.     /**
  608.      * Asks the workgroup for it's Offline Settings.
  609.      *
  610.      * @return offlineSettings the offline settings for this workgroup.
  611.      * @throws XMPPErrorException
  612.      * @throws NoResponseException
  613.      * @throws NotConnectedException
  614.      * @throws InterruptedException
  615.      */
  616.     public OfflineSettings getOfflineSettings() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  617.         OfflineSettings request = new OfflineSettings();
  618.         request.setType(IQ.Type.get);
  619.         request.setTo(workgroupJID);

  620.         OfflineSettings response = (OfflineSettings) connection.createPacketCollectorAndSend(
  621.                         request).nextResultOrThrow();
  622.         return response;
  623.     }

  624.     /**
  625.      * Asks the workgroup for it's Sound Settings.
  626.      *
  627.      * @return soundSettings the sound settings for the specified workgroup.
  628.      * @throws XMPPErrorException
  629.      * @throws NoResponseException
  630.      * @throws NotConnectedException
  631.      * @throws InterruptedException
  632.      */
  633.     public SoundSettings getSoundSettings() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  634.         SoundSettings request = new SoundSettings();
  635.         request.setType(IQ.Type.get);
  636.         request.setTo(workgroupJID);

  637.         SoundSettings response = (SoundSettings) connection.createPacketCollectorAndSend(request).nextResultOrThrow();
  638.         return response;
  639.     }

  640.     /**
  641.      * Asks the workgroup for it's Properties
  642.      *
  643.      * @return the WorkgroupProperties for the specified workgroup.
  644.      * @throws XMPPErrorException
  645.      * @throws NoResponseException
  646.      * @throws NotConnectedException
  647.      * @throws InterruptedException
  648.      */
  649.     public WorkgroupProperties getWorkgroupProperties() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  650.         WorkgroupProperties request = new WorkgroupProperties();
  651.         request.setType(IQ.Type.get);
  652.         request.setTo(workgroupJID);

  653.         WorkgroupProperties response = (WorkgroupProperties) connection.createPacketCollectorAndSend(
  654.                         request).nextResultOrThrow();
  655.         return response;
  656.     }

  657.     /**
  658.      * Asks the workgroup for it's Properties
  659.      *
  660.      * @param jid the jid of the user who's information you would like the workgroup to retreive.
  661.      * @return the WorkgroupProperties for the specified workgroup.
  662.      * @throws XMPPErrorException
  663.      * @throws NoResponseException
  664.      * @throws NotConnectedException
  665.      * @throws InterruptedException
  666.      */
  667.     public WorkgroupProperties getWorkgroupProperties(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  668.         WorkgroupProperties request = new WorkgroupProperties();
  669.         request.setJid(jid);
  670.         request.setType(IQ.Type.get);
  671.         request.setTo(workgroupJID);

  672.         WorkgroupProperties response = (WorkgroupProperties) connection.createPacketCollectorAndSend(
  673.                         request).nextResultOrThrow();
  674.         return response;
  675.     }


  676.     /**
  677.      * Returns the Form to use for all clients of a workgroup. It is unlikely that the server
  678.      * will change the form (without a restart) so it is safe to keep the returned form
  679.      * for future submissions.
  680.      *
  681.      * @return the Form to use for searching transcripts.
  682.      * @throws XMPPErrorException
  683.      * @throws NoResponseException
  684.      * @throws NotConnectedException
  685.      * @throws InterruptedException
  686.      */
  687.     public Form getWorkgroupForm() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  688.         WorkgroupForm workgroupForm = new WorkgroupForm();
  689.         workgroupForm.setType(IQ.Type.get);
  690.         workgroupForm.setTo(workgroupJID);

  691.         WorkgroupForm response = (WorkgroupForm) connection.createPacketCollectorAndSend(
  692.                         workgroupForm).nextResultOrThrow();
  693.         return Form.getFormFrom(response);
  694.     }
  695. }