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