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