001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.workgroup.user;
018
019import java.util.Iterator;
020import java.util.List;
021import java.util.Map;
022import java.util.concurrent.CopyOnWriteArraySet;
023
024import org.jivesoftware.smack.SmackException;
025import org.jivesoftware.smack.SmackException.NoResponseException;
026import org.jivesoftware.smack.SmackException.NotConnectedException;
027import org.jivesoftware.smack.StanzaCollector;
028import org.jivesoftware.smack.StanzaListener;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.XMPPException;
031import org.jivesoftware.smack.XMPPException.XMPPErrorException;
032import org.jivesoftware.smack.filter.AndFilter;
033import org.jivesoftware.smack.filter.FromMatchesFilter;
034import org.jivesoftware.smack.filter.StanzaFilter;
035import org.jivesoftware.smack.filter.StanzaTypeFilter;
036import org.jivesoftware.smack.packet.ExtensionElement;
037import org.jivesoftware.smack.packet.IQ;
038import org.jivesoftware.smack.packet.Message;
039import org.jivesoftware.smack.packet.Presence;
040import org.jivesoftware.smack.packet.Stanza;
041
042import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
043import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
044import org.jivesoftware.smackx.muc.MultiUserChatManager;
045import org.jivesoftware.smackx.muc.packet.MUCUser;
046import org.jivesoftware.smackx.workgroup.MetaData;
047import org.jivesoftware.smackx.workgroup.WorkgroupInvitation;
048import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener;
049import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm;
050import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket;
051import org.jivesoftware.smackx.workgroup.packet.QueueUpdate;
052import org.jivesoftware.smackx.workgroup.packet.SessionID;
053import org.jivesoftware.smackx.workgroup.packet.UserID;
054import org.jivesoftware.smackx.workgroup.settings.ChatSetting;
055import org.jivesoftware.smackx.workgroup.settings.ChatSettings;
056import org.jivesoftware.smackx.workgroup.settings.OfflineSettings;
057import org.jivesoftware.smackx.workgroup.settings.SoundSettings;
058import org.jivesoftware.smackx.workgroup.settings.WorkgroupProperties;
059import org.jivesoftware.smackx.xdata.Form;
060import org.jivesoftware.smackx.xdata.FormField;
061import org.jivesoftware.smackx.xdata.packet.DataForm;
062
063import org.jxmpp.jid.DomainBareJid;
064import org.jxmpp.jid.EntityJid;
065import org.jxmpp.jid.Jid;
066
067/**
068 * Provides workgroup services for users. Users can join the workgroup queue, depart the
069 * queue, find status information about their placement in the queue, and register to
070 * be notified when they are routed to an agent.<p>
071 *
072 * This class only provides a users perspective into a workgroup and is not intended
073 * for use by agents.
074 *
075 * @author Matt Tucker
076 * @author Derek DeMoro
077 */
078public class Workgroup {
079
080    private final Jid workgroupJID;
081    private final XMPPConnection connection;
082    private boolean inQueue;
083    private final CopyOnWriteArraySet<WorkgroupInvitationListener> invitationListeners;
084    private final CopyOnWriteArraySet<QueueListener> queueListeners;
085
086    private int queuePosition = -1;
087    private int queueRemainingTime = -1;
088
089    /**
090     * Creates a new workgroup instance using the specified workgroup JID
091     * (eg support@workgroup.example.com) and XMPP connection. The connection must have
092     * undergone a successful login before being used to construct an instance of
093     * this class.
094     *
095     * @param workgroupJID the JID of the workgroup.
096     * @param connection   an XMPP connection which must have already undergone a
097     *                     successful login.
098     */
099    public Workgroup(Jid workgroupJID, XMPPConnection connection) {
100        // Login must have been done before passing in connection.
101        if (!connection.isAuthenticated()) {
102            throw new IllegalStateException("Must login to server before creating workgroup.");
103        }
104
105        this.workgroupJID = workgroupJID;
106        this.connection = connection;
107        inQueue = false;
108        invitationListeners = new CopyOnWriteArraySet<>();
109        queueListeners = new CopyOnWriteArraySet<>();
110
111        // Register as a queue listener for internal usage by this instance.
112        addQueueListener(new QueueListener() {
113            @Override
114            public void joinedQueue() {
115                inQueue = true;
116            }
117
118            @Override
119            public void departedQueue() {
120                inQueue = false;
121                queuePosition = -1;
122                queueRemainingTime = -1;
123            }
124
125            @Override
126            public void queuePositionUpdated(int currentPosition) {
127                queuePosition = currentPosition;
128            }
129
130            @Override
131            public void queueWaitTimeUpdated(int secondsRemaining) {
132                queueRemainingTime = secondsRemaining;
133            }
134        });
135
136        /**
137         * Internal handling of an invitation.Recieving an invitation removes the user from the queue.
138         */
139        MultiUserChatManager.getInstanceFor(connection).addInvitationListener(
140                new org.jivesoftware.smackx.muc.InvitationListener() {
141                    @Override
142                    public void invitationReceived(XMPPConnection conn, org.jivesoftware.smackx.muc.MultiUserChat room, EntityJid inviter,
143                                                   String reason, String password, Message message, MUCUser.Invite invitation) {
144                        inQueue = false;
145                        queuePosition = -1;
146                        queueRemainingTime = -1;
147                    }
148                });
149
150        // Register a packet listener for all the messages sent to this client.
151        StanzaFilter typeFilter = new StanzaTypeFilter(Message.class);
152
153        connection.addAsyncStanzaListener(new StanzaListener() {
154            @Override
155            public void processStanza(Stanza packet) {
156                handlePacket(packet);
157            }
158        }, typeFilter);
159    }
160
161    /**
162     * Returns the name of this workgroup (eg support@example.com).
163     *
164     * @return the name of the workgroup.
165     */
166    public Jid getWorkgroupJID() {
167        return workgroupJID;
168    }
169
170    /**
171     * Returns true if the user is currently waiting in the workgroup queue.
172     *
173     * @return true if currently waiting in the queue.
174     */
175    public boolean isInQueue() {
176        return inQueue;
177    }
178
179    /**
180     * Returns true if the workgroup is available for receiving new requests. The workgroup will be
181     * available only when agents are available for this workgroup.
182     *
183     * @return true if the workgroup is available for receiving new requests.
184     * @throws XMPPErrorException 
185     * @throws NoResponseException 
186     * @throws NotConnectedException 
187     * @throws InterruptedException 
188     */
189    public boolean isAvailable() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
190        Presence directedPresence = new Presence(Presence.Type.available);
191        directedPresence.setTo(workgroupJID);
192        StanzaFilter typeFilter = new StanzaTypeFilter(Presence.class);
193        StanzaFilter fromFilter = FromMatchesFilter.create(workgroupJID);
194        StanzaCollector collector = connection.createStanzaCollectorAndSend(new AndFilter(fromFilter,
195                typeFilter), directedPresence);
196
197        Presence response = collector.nextResultOrThrow();
198        return Presence.Type.available == response.getType();
199    }
200
201    /**
202     * Returns the users current position in the workgroup queue. A value of 0 means
203     * the user is next in line to be routed; therefore, if the queue position
204     * is being displayed to the end user it is usually a good idea to add 1 to
205     * the value this method returns before display. If the user is not currently
206     * waiting in the workgroup, or no queue position information is available, -1
207     * will be returned.
208     *
209     * @return the user's current position in the workgroup queue, or -1 if the
210     *         position isn't available or if the user isn't in the queue.
211     */
212    public int getQueuePosition() {
213        return queuePosition;
214    }
215
216    /**
217     * Returns the estimated time (in seconds) that the user has to left wait in
218     * the workgroup queue before being routed. If the user is not currently waiting
219     * int he workgroup, or no queue time information is available, -1 will be
220     * returned.
221     *
222     * @return the estimated time remaining (in seconds) that the user has to
223     *         wait in the workgroup queue, or -1 if time information isn't available
224     *         or if the user isn't int the queue.
225     */
226    public int getQueueRemainingTime() {
227        return queueRemainingTime;
228    }
229
230    /**
231     * Joins the workgroup queue to wait to be routed to an agent. After joining
232     * the queue, queue status events will be sent to indicate the user's position and
233     * estimated time left in the queue. Once joining the queue, there are three ways
234     * the user can leave the queue: <ul>
235     *
236     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
237     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
238     * <li>A server error occurs, or an administrator explicitly removes the user
239     * from the queue.
240     * </ul>
241     *
242     * A user cannot request to join the queue again if already in the queue. Therefore,
243     * this method will throw an IllegalStateException if the user is already in the queue.<p>
244     *
245     * Some servers may be configured to require certain meta-data in order to
246     * join the queue. In that case, the {@link #joinQueue(Form)} method should be
247     * used instead of this method so that meta-data may be passed in.<p>
248     *
249     * The server tracks the conversations that a user has with agents over time. By
250     * default, that tracking is done using the user's JID. However, this is not always
251     * possible. For example, when the user is logged in anonymously using a web client.
252     * In that case the user ID might be a randomly generated value put into a persistent
253     * cookie or a username obtained via the session. A userID can be explicitly
254     * passed in by using the {@link #joinQueue(Form, Jid)} method. When specified,
255     * that userID will be used instead of the user's JID to track conversations. The
256     * server will ignore a manually specified userID if the user's connection to the server
257     * is not anonymous.
258     *
259     * @throws XMPPException if an error occurred joining the queue. An error may indicate
260     *                       that a connection failure occurred or that the server explicitly rejected the
261     *                       request to join the queue.
262     * @throws SmackException 
263     * @throws InterruptedException 
264     */
265    public void joinQueue() throws XMPPException, SmackException, InterruptedException {
266        joinQueue(null);
267    }
268
269    /**
270     * Joins the workgroup queue to wait to be routed to an agent. After joining
271     * the queue, queue status events will be sent to indicate the user's position and
272     * estimated time left in the queue. Once joining the queue, there are three ways
273     * the user can leave the queue: <ul>
274     *
275     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
276     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
277     * <li>A server error occurs, or an administrator explicitly removes the user
278     * from the queue.
279     * </ul>
280     *
281     * A user cannot request to join the queue again if already in the queue. Therefore,
282     * this method will throw an IllegalStateException if the user is already in the queue.<p>
283     *
284     * Some servers may be configured to require certain meta-data in order to
285     * join the queue.<p>
286     *
287     * The server tracks the conversations that a user has with agents over time. By
288     * default, that tracking is done using the user's JID. However, this is not always
289     * possible. For example, when the user is logged in anonymously using a web client.
290     * In that case the user ID might be a randomly generated value put into a persistent
291     * cookie or a username obtained via the session. A userID can be explicitly
292     * passed in by using the {@link #joinQueue(Form, Jid)} method. When specified,
293     * that userID will be used instead of the user's JID to track conversations. The
294     * server will ignore a manually specified userID if the user's connection to the server
295     * is not anonymous.
296     *
297     * @param answerForm the completed form the send for the join request.
298     * @throws XMPPException if an error occurred joining the queue. An error may indicate
299     *                       that a connection failure occurred or that the server explicitly rejected the
300     *                       request to join the queue.
301     * @throws SmackException 
302     * @throws InterruptedException 
303     */
304    public void joinQueue(Form answerForm) throws XMPPException, SmackException, InterruptedException {
305        joinQueue(answerForm, null);
306    }
307
308    /**
309     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
310     * the queue, queue status events will be sent to indicate the user's position and
311     * estimated time left in the queue. Once joining the queue, there are three ways
312     * the user can leave the queue: <ul>
313     *
314     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
315     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
316     * <li>A server error occurs, or an administrator explicitly removes the user
317     * from the queue.
318     * </ul>
319     *
320     * A user cannot request to join the queue again if already in the queue. Therefore,
321     * this method will throw an IllegalStateException if the user is already in the queue.<p>
322     *
323     * Some servers may be configured to require certain meta-data in order to
324     * join the queue.<p>
325     *
326     * The server tracks the conversations that a user has with agents over time. By
327     * default, that tracking is done using the user's JID. However, this is not always
328     * possible. For example, when the user is logged in anonymously using a web client.
329     * In that case the user ID might be a randomly generated value put into a persistent
330     * cookie or a username obtained via the session. When specified, that userID will
331     * be used instead of the user's JID to track conversations. The server will ignore a
332     * manually specified userID if the user's connection to the server is not anonymous.
333     *
334     * @param answerForm the completed form associated with the join request.
335     * @param userID     String that represents the ID of the user when using anonymous sessions
336     *                   or <tt>null</tt> if a userID should not be used.
337     * @throws XMPPErrorException if an error occurred joining the queue. An error may indicate
338     *                       that a connection failure occurred or that the server explicitly rejected the
339     *                       request to join the queue.
340     * @throws NoResponseException 
341     * @throws NotConnectedException 
342     * @throws InterruptedException 
343     */
344    public void joinQueue(Form answerForm, Jid userID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
345        // If already in the queue ignore the join request.
346        if (inQueue) {
347            throw new IllegalStateException("Already in queue " + workgroupJID);
348        }
349
350        JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID);
351
352        connection.createStanzaCollectorAndSend(joinPacket).nextResultOrThrow();
353        // Notify listeners that we've joined the queue.
354        fireQueueJoinedEvent();
355    }
356
357    /**
358     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
359     * the queue, queue status events will be sent to indicate the user's position and
360     * estimated time left in the queue. Once joining the queue, there are three ways
361     * the user can leave the queue: <ul>
362     *
363     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
364     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
365     * <li>A server error occurs, or an administrator explicitly removes the user
366     * from the queue.
367     * </ul>
368     *
369     * A user cannot request to join the queue again if already in the queue. Therefore,
370     * this method will throw an IllegalStateException if the user is already in the queue.<p>
371     *
372     * Some servers may be configured to require certain meta-data in order to
373     * join the queue.<p>
374     *
375     * The server tracks the conversations that a user has with agents over time. By
376     * default, that tracking is done using the user's JID. However, this is not always
377     * possible. For example, when the user is logged in anonymously using a web client.
378     * In that case the user ID might be a randomly generated value put into a persistent
379     * cookie or a username obtained via the session. When specified, that userID will
380     * be used instead of the user's JID to track conversations. The server will ignore a
381     * manually specified userID if the user's connection to the server is not anonymous.
382     *
383     * @param metadata metadata to create a dataform from.
384     * @param userID   String that represents the ID of the user when using anonymous sessions
385     *                 or <tt>null</tt> if a userID should not be used.
386     * @throws XMPPException if an error occurred joining the queue. An error may indicate
387     *                       that a connection failure occurred or that the server explicitly rejected the
388     *                       request to join the queue.
389     * @throws SmackException 
390     * @throws InterruptedException 
391     */
392    public void joinQueue(Map<String,Object> metadata, Jid userID) throws XMPPException, SmackException, InterruptedException {
393        // If already in the queue ignore the join request.
394        if (inQueue) {
395            throw new IllegalStateException("Already in queue " + workgroupJID);
396        }
397
398        // Build dataform from metadata
399        Form form = new Form(DataForm.Type.submit);
400        Iterator<String> iter = metadata.keySet().iterator();
401        while (iter.hasNext()) {
402            String name = iter.next();
403            String value = metadata.get(name).toString();
404
405            FormField field = new FormField(name);
406            field.setType(FormField.Type.text_single);
407            form.addField(field);
408            form.setAnswer(name, value);
409        }
410        joinQueue(form, userID);
411    }
412
413    /**
414     * Departs the workgroup queue. If the user is not currently in the queue, this
415     * method will do nothing.<p>
416     *
417     * Normally, the user would not manually leave the queue. However, they may wish to
418     * under certain circumstances -- for example, if they no longer wish to be routed
419     * to an agent because they've been waiting too long.
420     *
421     * @throws XMPPErrorException if an error occurred trying to send the depart queue
422     *                       request to the server.
423     * @throws NoResponseException 
424     * @throws NotConnectedException 
425     * @throws InterruptedException 
426     */
427    public void departQueue() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
428        // If not in the queue ignore the depart request.
429        if (!inQueue) {
430            return;
431        }
432
433        DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
434        connection.createStanzaCollectorAndSend(departPacket).nextResultOrThrow();
435
436        // Notify listeners that we're no longer in the queue.
437        fireQueueDepartedEvent();
438    }
439
440    /**
441     * Adds a queue listener that will be notified of queue events for the user
442     * that created this Workgroup instance.
443     *
444     * @param queueListener the queue listener.
445     */
446    public void addQueueListener(QueueListener queueListener) {
447        queueListeners.add(queueListener);
448    }
449
450    /**
451     * Removes a queue listener.
452     *
453     * @param queueListener the queue listener.
454     */
455    public void removeQueueListener(QueueListener queueListener) {
456        queueListeners.remove(queueListener);
457    }
458
459    /**
460     * Adds an invitation listener that will be notified of groupchat invitations
461     * from the workgroup for the the user that created this Workgroup instance.
462     *
463     * @param invitationListener the invitation listener.
464     */
465    public void addInvitationListener(WorkgroupInvitationListener invitationListener) {
466        invitationListeners.add(invitationListener);
467    }
468
469    /**
470     * Removes an invitation listener.
471     *
472     * @param invitationListener the invitation listener.
473     */
474    public void removeQueueListener(WorkgroupInvitationListener invitationListener) {
475        invitationListeners.remove(invitationListener);
476    }
477
478    private void fireInvitationEvent(WorkgroupInvitation invitation) {
479        for (WorkgroupInvitationListener listener : invitationListeners) {
480            // CHECKSTYLE:OFF
481            listener.invitationReceived(invitation);
482            // CHECKSTYLE:ON
483        }
484    }
485
486    private void fireQueueJoinedEvent() {
487        for (QueueListener listener : queueListeners) {
488            // CHECKSTYLE:OFF
489            listener.joinedQueue();
490            // CHECKSTYLE:ON
491        }
492    }
493
494    private void fireQueueDepartedEvent() {
495        for (QueueListener listener : queueListeners) {
496            listener.departedQueue();
497        }
498    }
499
500    private void fireQueuePositionEvent(int currentPosition) {
501        for (QueueListener listener : queueListeners) {
502            listener.queuePositionUpdated(currentPosition);
503        }
504    }
505
506    private void fireQueueTimeEvent(int secondsRemaining) {
507        for (QueueListener listener : queueListeners) {
508            listener.queueWaitTimeUpdated(secondsRemaining);
509        }
510    }
511
512    // PacketListener Implementation.
513
514    private void handlePacket(Stanza packet) {
515        if (packet instanceof Message) {
516            Message msg = (Message) packet;
517            // Check to see if the user left the queue.
518            ExtensionElement pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup");
519            ExtensionElement queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup");
520
521            if (pe != null) {
522                fireQueueDepartedEvent();
523            }
524            else if (queueStatus != null) {
525                QueueUpdate queueUpdate = (QueueUpdate) queueStatus;
526                if (queueUpdate.getPosition() != -1) {
527                    fireQueuePositionEvent(queueUpdate.getPosition());
528                }
529                if (queueUpdate.getRemaingTime() != -1) {
530                    fireQueueTimeEvent(queueUpdate.getRemaingTime());
531                }
532            }
533
534            else {
535                // Check if a room invitation was sent and if the sender is the workgroup
536                MUCUser mucUser = msg.getExtension("x", "http://jabber.org/protocol/muc#user");
537                MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null;
538                if (invite != null && workgroupJID.equals(invite.getFrom())) {
539                    String sessionID = null;
540                    Map<String, List<String>> metaData = null;
541
542                    pe = msg.getExtension(SessionID.ELEMENT_NAME,
543                            SessionID.NAMESPACE);
544                    if (pe != null) {
545                        sessionID = ((SessionID) pe).getSessionID();
546                    }
547
548                    pe = msg.getExtension(MetaData.ELEMENT_NAME,
549                            MetaData.NAMESPACE);
550                    if (pe != null) {
551                        metaData = ((MetaData) pe).getMetaData();
552                    }
553
554                    WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(),
555                            workgroupJID, sessionID, msg.getBody(),
556                            msg.getFrom(), metaData);
557
558                    fireInvitationEvent(inv);
559                }
560            }
561        }
562    }
563
564    /**
565     * IQ stanza(/packet) to request joining the workgroup queue.
566     */
567    private class JoinQueuePacket extends IQ {
568
569        private final Jid userID;
570        private final DataForm form;
571
572        public JoinQueuePacket(Jid workgroup, Form answerForm, Jid userID) {
573            super("join-queue", "http://jabber.org/protocol/workgroup");
574            this.userID = userID;
575
576            setTo(workgroup);
577            setType(IQ.Type.set);
578
579            form = answerForm.getDataFormToSend();
580            addExtension(form);
581        }
582
583        @Override
584        protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
585            buf.rightAngleBracket();
586            buf.append("<queue-notifications/>");
587            // Add the user unique identification if the session is anonymous
588            if (connection.isAnonymous()) {
589                buf.append(new UserID(userID).toXML());
590            }
591
592            // Append data form text
593            buf.append(form.toXML());
594
595            return buf;
596        }
597    }
598
599    /**
600     * Returns a single chat setting based on it's identified key.
601     *
602     * @param key the key to find.
603     * @return the ChatSetting if found, otherwise false.
604     * @throws XMPPException if an error occurs while getting information from the server.
605     * @throws SmackException 
606     * @throws InterruptedException 
607     */
608    public ChatSetting getChatSetting(String key) throws XMPPException, SmackException, InterruptedException {
609        ChatSettings chatSettings = getChatSettings(key, -1);
610        return chatSettings.getFirstEntry();
611    }
612
613    /**
614     * Returns ChatSettings based on type.
615     *
616     * @param type the type of ChatSettings to return.
617     * @return the ChatSettings of given type, otherwise null.
618     * @throws XMPPException if an error occurs while getting information from the server.
619     * @throws SmackException 
620     * @throws InterruptedException 
621     */
622    public ChatSettings getChatSettings(int type) throws XMPPException, SmackException, InterruptedException {
623        return getChatSettings(null, type);
624    }
625
626    /**
627     * Returns all ChatSettings.
628     *
629     * @return all ChatSettings of a given workgroup.
630     * @throws XMPPException if an error occurs while getting information from the server.
631     * @throws SmackException 
632     * @throws InterruptedException 
633     */
634    public ChatSettings getChatSettings() throws XMPPException, SmackException, InterruptedException {
635        return getChatSettings(null, -1);
636    }
637
638
639    /**
640     * Asks the workgroup for it's Chat Settings.
641     *
642     * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null.
643     * @throws NoResponseException 
644     * @throws XMPPErrorException if an error occurs while getting information from the server.
645     * @throws NotConnectedException 
646     * @throws InterruptedException 
647     */
648    private ChatSettings getChatSettings(String key, int type) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
649        ChatSettings request = new ChatSettings();
650        if (key != null) {
651            request.setKey(key);
652        }
653        if (type != -1) {
654            request.setType(type);
655        }
656        request.setType(IQ.Type.get);
657        request.setTo(workgroupJID);
658
659        ChatSettings response = connection.createStanzaCollectorAndSend(request).nextResultOrThrow();
660
661        return response;
662    }
663
664    /**
665     * The workgroup service may be configured to send email. This queries the Workgroup Service
666     * to see if the email service has been configured and is available.
667     *
668     * @return true if the email service is available, otherwise return false.
669     * @throws SmackException 
670     * @throws InterruptedException 
671     */
672    public boolean isEmailAvailable() throws SmackException, InterruptedException {
673        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
674
675        try {
676            DomainBareJid workgroupService = workgroupJID.asDomainBareJid();
677            DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService);
678            return infoResult.containsFeature("jive:email:provider");
679        }
680        catch (XMPPException e) {
681            return false;
682        }
683    }
684
685    /**
686     * Asks the workgroup for it's Offline Settings.
687     *
688     * @return offlineSettings the offline settings for this workgroup.
689     * @throws XMPPErrorException 
690     * @throws NoResponseException 
691     * @throws NotConnectedException 
692     * @throws InterruptedException 
693     */
694    public OfflineSettings getOfflineSettings() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
695        OfflineSettings request = new OfflineSettings();
696        request.setType(IQ.Type.get);
697        request.setTo(workgroupJID);
698
699        return connection.createStanzaCollectorAndSend(request).nextResultOrThrow();
700    }
701
702    /**
703     * Asks the workgroup for it's Sound Settings.
704     *
705     * @return soundSettings the sound settings for the specified workgroup.
706     * @throws XMPPErrorException 
707     * @throws NoResponseException 
708     * @throws NotConnectedException 
709     * @throws InterruptedException 
710     */
711    public SoundSettings getSoundSettings() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
712        SoundSettings request = new SoundSettings();
713        request.setType(IQ.Type.get);
714        request.setTo(workgroupJID);
715
716        return connection.createStanzaCollectorAndSend(request).nextResultOrThrow();
717    }
718
719    /**
720     * Asks the workgroup for it's Properties.
721     *
722     * @return the WorkgroupProperties for the specified workgroup.
723     * @throws XMPPErrorException
724     * @throws NoResponseException
725     * @throws NotConnectedException 
726     * @throws InterruptedException 
727     */
728    public WorkgroupProperties getWorkgroupProperties() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
729        WorkgroupProperties request = new WorkgroupProperties();
730        request.setType(IQ.Type.get);
731        request.setTo(workgroupJID);
732
733        return connection.createStanzaCollectorAndSend(request).nextResultOrThrow();
734    }
735
736    /**
737     * Asks the workgroup for it's Properties.
738     *
739     * @param jid the jid of the user who's information you would like the workgroup to retreive.
740     * @return the WorkgroupProperties for the specified workgroup.
741     * @throws XMPPErrorException
742     * @throws NoResponseException
743     * @throws NotConnectedException 
744     * @throws InterruptedException 
745     */
746    public WorkgroupProperties getWorkgroupProperties(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
747        WorkgroupProperties request = new WorkgroupProperties();
748        request.setJid(jid);
749        request.setType(IQ.Type.get);
750        request.setTo(workgroupJID);
751
752        return connection.createStanzaCollectorAndSend(
753                        request).nextResultOrThrow();
754    }
755
756
757    /**
758     * Returns the Form to use for all clients of a workgroup. It is unlikely that the server
759     * will change the form (without a restart) so it is safe to keep the returned form
760     * for future submissions.
761     *
762     * @return the Form to use for searching transcripts.
763     * @throws XMPPErrorException
764     * @throws NoResponseException
765     * @throws NotConnectedException 
766     * @throws InterruptedException 
767     */
768    public Form getWorkgroupForm() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
769        WorkgroupForm workgroupForm = new WorkgroupForm();
770        workgroupForm.setType(IQ.Type.get);
771        workgroupForm.setTo(workgroupJID);
772
773        WorkgroupForm response = connection.createStanzaCollectorAndSend(
774                        workgroupForm).nextResultOrThrow();
775        return Form.getFormFrom(response);
776    }
777}