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