1/**
2 * Copyright 2003-2007 Jive Software.
3 *
4 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.jivesoftware.smackx.workgroup.user;
18
19import org.jivesoftware.smackx.workgroup.MetaData;
20import org.jivesoftware.smackx.workgroup.WorkgroupInvitation;
21import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener;
22import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm;
23import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket;
24import org.jivesoftware.smackx.workgroup.packet.QueueUpdate;
25import org.jivesoftware.smackx.workgroup.packet.SessionID;
26import org.jivesoftware.smackx.workgroup.packet.UserID;
27import org.jivesoftware.smackx.workgroup.settings.*;
28import org.jivesoftware.smack.*;
29import org.jivesoftware.smack.filter.*;
30import org.jivesoftware.smack.packet.*;
31import org.jivesoftware.smack.util.StringUtils;
32import org.jivesoftware.smackx.Form;
33import org.jivesoftware.smackx.FormField;
34import org.jivesoftware.smackx.ServiceDiscoveryManager;
35import org.jivesoftware.smackx.muc.MultiUserChat;
36import org.jivesoftware.smackx.packet.DataForm;
37import org.jivesoftware.smackx.packet.DiscoverInfo;
38import org.jivesoftware.smackx.packet.MUCUser;
39
40import java.util.ArrayList;
41import java.util.Iterator;
42import java.util.List;
43import java.util.Map;
44
45/**
46 * Provides workgroup services for users. Users can join the workgroup queue, depart the
47 * queue, find status information about their placement in the queue, and register to
48 * be notified when they are routed to an agent.<p>
49 * <p/>
50 * This class only provides a users perspective into a workgroup and is not intended
51 * for use by agents.
52 *
53 * @author Matt Tucker
54 * @author Derek DeMoro
55 */
56public class Workgroup {
57
58    private String workgroupJID;
59    private Connection connection;
60    private boolean inQueue;
61    private List<WorkgroupInvitationListener> invitationListeners;
62    private List<QueueListener> queueListeners;
63
64    private int queuePosition = -1;
65    private int queueRemainingTime = -1;
66
67    /**
68     * Creates a new workgroup instance using the specified workgroup JID
69     * (eg support@workgroup.example.com) and XMPP connection. The connection must have
70     * undergone a successful login before being used to construct an instance of
71     * this class.
72     *
73     * @param workgroupJID the JID of the workgroup.
74     * @param connection   an XMPP connection which must have already undergone a
75     *                     successful login.
76     */
77    public Workgroup(String workgroupJID, Connection connection) {
78        // Login must have been done before passing in connection.
79        if (!connection.isAuthenticated()) {
80            throw new IllegalStateException("Must login to server before creating workgroup.");
81        }
82
83        this.workgroupJID = workgroupJID;
84        this.connection = connection;
85        inQueue = false;
86        invitationListeners = new ArrayList<WorkgroupInvitationListener>();
87        queueListeners = new ArrayList<QueueListener>();
88
89        // Register as a queue listener for internal usage by this instance.
90        addQueueListener(new QueueListener() {
91            public void joinedQueue() {
92                inQueue = true;
93            }
94
95            public void departedQueue() {
96                inQueue = false;
97                queuePosition = -1;
98                queueRemainingTime = -1;
99            }
100
101            public void queuePositionUpdated(int currentPosition) {
102                queuePosition = currentPosition;
103            }
104
105            public void queueWaitTimeUpdated(int secondsRemaining) {
106                queueRemainingTime = secondsRemaining;
107            }
108        });
109
110        /**
111         * Internal handling of an invitation.Recieving an invitation removes the user from the queue.
112         */
113        MultiUserChat.addInvitationListener(connection,
114                new org.jivesoftware.smackx.muc.InvitationListener() {
115                    public void invitationReceived(Connection conn, String room, String inviter,
116                                                   String reason, String password, Message message) {
117                        inQueue = false;
118                        queuePosition = -1;
119                        queueRemainingTime = -1;
120                    }
121                });
122
123        // Register a packet listener for all the messages sent to this client.
124        PacketFilter typeFilter = new PacketTypeFilter(Message.class);
125
126        connection.addPacketListener(new PacketListener() {
127            public void processPacket(Packet packet) {
128                handlePacket(packet);
129            }
130        }, typeFilter);
131    }
132
133    /**
134     * Returns the name of this workgroup (eg support@example.com).
135     *
136     * @return the name of the workgroup.
137     */
138    public String getWorkgroupJID() {
139        return workgroupJID;
140    }
141
142    /**
143     * Returns true if the user is currently waiting in the workgroup queue.
144     *
145     * @return true if currently waiting in the queue.
146     */
147    public boolean isInQueue() {
148        return inQueue;
149    }
150
151    /**
152     * Returns true if the workgroup is available for receiving new requests. The workgroup will be
153     * available only when agents are available for this workgroup.
154     *
155     * @return true if the workgroup is available for receiving new requests.
156     */
157    public boolean isAvailable() {
158        Presence directedPresence = new Presence(Presence.Type.available);
159        directedPresence.setTo(workgroupJID);
160        PacketFilter typeFilter = new PacketTypeFilter(Presence.class);
161        PacketFilter fromFilter = new FromContainsFilter(workgroupJID);
162        PacketCollector collector = connection.createPacketCollector(new AndFilter(fromFilter,
163                typeFilter));
164
165        connection.sendPacket(directedPresence);
166
167        Presence response = (Presence)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
168
169        // Cancel the collector.
170        collector.cancel();
171        if (response == null) {
172            return false;
173        }
174        else if (response.getError() != null) {
175            return false;
176        }
177        else {
178            return Presence.Type.available == response.getType();
179        }
180    }
181
182    /**
183     * Returns the users current position in the workgroup queue. A value of 0 means
184     * the user is next in line to be routed; therefore, if the queue position
185     * is being displayed to the end user it is usually a good idea to add 1 to
186     * the value this method returns before display. If the user is not currently
187     * waiting in the workgroup, or no queue position information is available, -1
188     * will be returned.
189     *
190     * @return the user's current position in the workgroup queue, or -1 if the
191     *         position isn't available or if the user isn't in the queue.
192     */
193    public int getQueuePosition() {
194        return queuePosition;
195    }
196
197    /**
198     * Returns the estimated time (in seconds) that the user has to left wait in
199     * the workgroup queue before being routed. If the user is not currently waiting
200     * int he workgroup, or no queue time information is available, -1 will be
201     * returned.
202     *
203     * @return the estimated time remaining (in seconds) that the user has to
204     *         wait inthe workgroupu queue, or -1 if time information isn't available
205     *         or if the user isn't int the queue.
206     */
207    public int getQueueRemainingTime() {
208        return queueRemainingTime;
209    }
210
211    /**
212     * Joins the workgroup queue to wait to be routed to an agent. After joining
213     * the queue, queue status events will be sent to indicate the user's position and
214     * estimated time left in the queue. Once joining the queue, there are three ways
215     * the user can leave the queue: <ul>
216     * <p/>
217     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
218     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
219     * <li>A server error occurs, or an administrator explicitly removes the user
220     * from the queue.
221     * </ul>
222     * <p/>
223     * A user cannot request to join the queue again if already in the queue. Therefore,
224     * this method will throw an IllegalStateException if the user is already in the queue.<p>
225     * <p/>
226     * Some servers may be configured to require certain meta-data in order to
227     * join the queue. In that case, the {@link #joinQueue(Form)} method should be
228     * used instead of this method so that meta-data may be passed in.<p>
229     * <p/>
230     * The server tracks the conversations that a user has with agents over time. By
231     * default, that tracking is done using the user's JID. However, this is not always
232     * possible. For example, when the user is logged in anonymously using a web client.
233     * In that case the user ID might be a randomly generated value put into a persistent
234     * cookie or a username obtained via the session. A userID can be explicitly
235     * passed in by using the {@link #joinQueue(Form, String)} method. When specified,
236     * that userID will be used instead of the user's JID to track conversations. The
237     * server will ignore a manually specified userID if the user's connection to the server
238     * is not anonymous.
239     *
240     * @throws XMPPException if an error occured joining the queue. An error may indicate
241     *                       that a connection failure occured or that the server explicitly rejected the
242     *                       request to join the queue.
243     */
244    public void joinQueue() throws XMPPException {
245        joinQueue(null);
246    }
247
248    /**
249     * Joins the workgroup queue to wait to be routed to an agent. After joining
250     * the queue, queue status events will be sent to indicate the user's position and
251     * estimated time left in the queue. Once joining the queue, there are three ways
252     * the user can leave the queue: <ul>
253     * <p/>
254     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
255     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
256     * <li>A server error occurs, or an administrator explicitly removes the user
257     * from the queue.
258     * </ul>
259     * <p/>
260     * A user cannot request to join the queue again if already in the queue. Therefore,
261     * this method will throw an IllegalStateException if the user is already in the queue.<p>
262     * <p/>
263     * Some servers may be configured to require certain meta-data in order to
264     * join the queue.<p>
265     * <p/>
266     * The server tracks the conversations that a user has with agents over time. By
267     * default, that tracking is done using the user's JID. However, this is not always
268     * possible. For example, when the user is logged in anonymously using a web client.
269     * In that case the user ID might be a randomly generated value put into a persistent
270     * cookie or a username obtained via the session. A userID can be explicitly
271     * passed in by using the {@link #joinQueue(Form, String)} method. When specified,
272     * that userID will be used instead of the user's JID to track conversations. The
273     * server will ignore a manually specified userID if the user's connection to the server
274     * is not anonymous.
275     *
276     * @param answerForm the completed form the send for the join request.
277     * @throws XMPPException if an error occured joining the queue. An error may indicate
278     *                       that a connection failure occured or that the server explicitly rejected the
279     *                       request to join the queue.
280     */
281    public void joinQueue(Form answerForm) throws XMPPException {
282        joinQueue(answerForm, null);
283    }
284
285    /**
286     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
287     * the queue, queue status events will be sent to indicate the user's position and
288     * estimated time left in the queue. Once joining the queue, there are three ways
289     * the user can leave the queue: <ul>
290     * <p/>
291     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
292     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
293     * <li>A server error occurs, or an administrator explicitly removes the user
294     * from the queue.
295     * </ul>
296     * <p/>
297     * A user cannot request to join the queue again if already in the queue. Therefore,
298     * this method will throw an IllegalStateException if the user is already in the queue.<p>
299     * <p/>
300     * Some servers may be configured to require certain meta-data in order to
301     * join the queue.<p>
302     * <p/>
303     * The server tracks the conversations that a user has with agents over time. By
304     * default, that tracking is done using the user's JID. However, this is not always
305     * possible. For example, when the user is logged in anonymously using a web client.
306     * In that case the user ID might be a randomly generated value put into a persistent
307     * cookie or a username obtained via the session. When specified, that userID will
308     * be used instead of the user's JID to track conversations. The server will ignore a
309     * manually specified userID if the user's connection to the server is not anonymous.
310     *
311     * @param answerForm the completed form associated with the join reqest.
312     * @param userID     String that represents the ID of the user when using anonymous sessions
313     *                   or <tt>null</tt> if a userID should not be used.
314     * @throws XMPPException if an error occured joining the queue. An error may indicate
315     *                       that a connection failure occured or that the server explicitly rejected the
316     *                       request to join the queue.
317     */
318    public void joinQueue(Form answerForm, String userID) throws XMPPException {
319        // If already in the queue ignore the join request.
320        if (inQueue) {
321            throw new IllegalStateException("Already in queue " + workgroupJID);
322        }
323
324        JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID);
325
326
327        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(joinPacket.getPacketID()));
328
329        this.connection.sendPacket(joinPacket);
330
331        IQ response = (IQ)collector.nextResult(10000);
332
333        // Cancel the collector.
334        collector.cancel();
335        if (response == null) {
336            throw new XMPPException("No response from the server.");
337        }
338        if (response.getError() != null) {
339            throw new XMPPException(response.getError());
340        }
341
342        // Notify listeners that we've joined the queue.
343        fireQueueJoinedEvent();
344    }
345
346    /**
347     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
348     * the queue, queue status events will be sent to indicate the user's position and
349     * estimated time left in the queue. Once joining the queue, there are three ways
350     * the user can leave the queue: <ul>
351     * <p/>
352     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
353     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
354     * <li>A server error occurs, or an administrator explicitly removes the user
355     * from the queue.
356     * </ul>
357     * <p/>
358     * A user cannot request to join the queue again if already in the queue. Therefore,
359     * this method will throw an IllegalStateException if the user is already in the queue.<p>
360     * <p/>
361     * Some servers may be configured to require certain meta-data in order to
362     * join the queue.<p>
363     * <p/>
364     * The server tracks the conversations that a user has with agents over time. By
365     * default, that tracking is done using the user's JID. However, this is not always
366     * possible. For example, when the user is logged in anonymously using a web client.
367     * In that case the user ID might be a randomly generated value put into a persistent
368     * cookie or a username obtained via the session. When specified, that userID will
369     * be used instead of the user's JID to track conversations. The server will ignore a
370     * manually specified userID if the user's connection to the server is not anonymous.
371     *
372     * @param metadata metadata to create a dataform from.
373     * @param userID   String that represents the ID of the user when using anonymous sessions
374     *                 or <tt>null</tt> if a userID should not be used.
375     * @throws XMPPException if an error occured joining the queue. An error may indicate
376     *                       that a connection failure occured or that the server explicitly rejected the
377     *                       request to join the queue.
378     */
379    public void joinQueue(Map<String,Object> metadata, String userID) throws XMPPException {
380        // If already in the queue ignore the join request.
381        if (inQueue) {
382            throw new IllegalStateException("Already in queue " + workgroupJID);
383        }
384
385        // Build dataform from metadata
386        Form form = new Form(Form.TYPE_SUBMIT);
387        Iterator<String> iter = metadata.keySet().iterator();
388        while (iter.hasNext()) {
389            String name = iter.next();
390            String value = metadata.get(name).toString();
391
392            String escapedName = StringUtils.escapeForXML(name);
393            String escapedValue = StringUtils.escapeForXML(value);
394
395            FormField field = new FormField(escapedName);
396            field.setType(FormField.TYPE_TEXT_SINGLE);
397            form.addField(field);
398            form.setAnswer(escapedName, escapedValue);
399        }
400        joinQueue(form, userID);
401    }
402
403    /**
404     * Departs the workgroup queue. If the user is not currently in the queue, this
405     * method will do nothing.<p>
406     * <p/>
407     * Normally, the user would not manually leave the queue. However, they may wish to
408     * under certain circumstances -- for example, if they no longer wish to be routed
409     * to an agent because they've been waiting too long.
410     *
411     * @throws XMPPException if an error occured trying to send the depart queue
412     *                       request to the server.
413     */
414    public void departQueue() throws XMPPException {
415        // If not in the queue ignore the depart request.
416        if (!inQueue) {
417            return;
418        }
419
420        DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
421        PacketCollector collector = this.connection.createPacketCollector(new PacketIDFilter(departPacket.getPacketID()));
422
423        connection.sendPacket(departPacket);
424
425        IQ response = (IQ)collector.nextResult(5000);
426        collector.cancel();
427        if (response == null) {
428            throw new XMPPException("No response from the server.");
429        }
430        if (response.getError() != null) {
431            throw new XMPPException(response.getError());
432        }
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        synchronized (queueListeners) {
446            if (!queueListeners.contains(queueListener)) {
447                queueListeners.add(queueListener);
448            }
449        }
450    }
451
452    /**
453     * Removes a queue listener.
454     *
455     * @param queueListener the queue listener.
456     */
457    public void removeQueueListener(QueueListener queueListener) {
458        synchronized (queueListeners) {
459            queueListeners.remove(queueListener);
460        }
461    }
462
463    /**
464     * Adds an invitation listener that will be notified of groupchat invitations
465     * from the workgroup for the the user that created this Workgroup instance.
466     *
467     * @param invitationListener the invitation listener.
468     */
469    public void addInvitationListener(WorkgroupInvitationListener invitationListener) {
470        synchronized (invitationListeners) {
471            if (!invitationListeners.contains(invitationListener)) {
472                invitationListeners.add(invitationListener);
473            }
474        }
475    }
476
477    /**
478     * Removes an invitation listener.
479     *
480     * @param invitationListener the invitation listener.
481     */
482    public void removeQueueListener(WorkgroupInvitationListener invitationListener) {
483        synchronized (invitationListeners) {
484            invitationListeners.remove(invitationListener);
485        }
486    }
487
488    private void fireInvitationEvent(WorkgroupInvitation invitation) {
489        synchronized (invitationListeners) {
490            for (Iterator<WorkgroupInvitationListener> i = invitationListeners.iterator(); i.hasNext();) {
491                WorkgroupInvitationListener listener = i.next();
492                listener.invitationReceived(invitation);
493            }
494        }
495    }
496
497    private void fireQueueJoinedEvent() {
498        synchronized (queueListeners) {
499            for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
500                QueueListener listener = i.next();
501                listener.joinedQueue();
502            }
503        }
504    }
505
506    private void fireQueueDepartedEvent() {
507        synchronized (queueListeners) {
508            for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
509                QueueListener listener = i.next();
510                listener.departedQueue();
511            }
512        }
513    }
514
515    private void fireQueuePositionEvent(int currentPosition) {
516        synchronized (queueListeners) {
517            for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
518                QueueListener listener = i.next();
519                listener.queuePositionUpdated(currentPosition);
520            }
521        }
522    }
523
524    private void fireQueueTimeEvent(int secondsRemaining) {
525        synchronized (queueListeners) {
526            for (Iterator<QueueListener> i = queueListeners.iterator(); i.hasNext();) {
527                QueueListener listener = i.next();
528                listener.queueWaitTimeUpdated(secondsRemaining);
529            }
530        }
531    }
532
533    // PacketListener Implementation.
534
535    private void handlePacket(Packet packet) {
536        if (packet instanceof Message) {
537            Message msg = (Message)packet;
538            // Check to see if the user left the queue.
539            PacketExtension pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup");
540            PacketExtension queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup");
541
542            if (pe != null) {
543                fireQueueDepartedEvent();
544            }
545            else if (queueStatus != null) {
546                QueueUpdate queueUpdate = (QueueUpdate)queueStatus;
547                if (queueUpdate.getPosition() != -1) {
548                    fireQueuePositionEvent(queueUpdate.getPosition());
549                }
550                if (queueUpdate.getRemaingTime() != -1) {
551                    fireQueueTimeEvent(queueUpdate.getRemaingTime());
552                }
553            }
554
555            else {
556                // Check if a room invitation was sent and if the sender is the workgroup
557                MUCUser mucUser = (MUCUser)msg.getExtension("x", "http://jabber.org/protocol/muc#user");
558                MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null;
559                if (invite != null && workgroupJID.equals(invite.getFrom())) {
560                    String sessionID = null;
561                    Map<String, List<String>> metaData = null;
562
563                    pe = msg.getExtension(SessionID.ELEMENT_NAME,
564                            SessionID.NAMESPACE);
565                    if (pe != null) {
566                        sessionID = ((SessionID)pe).getSessionID();
567                    }
568
569                    pe = msg.getExtension(MetaData.ELEMENT_NAME,
570                            MetaData.NAMESPACE);
571                    if (pe != null) {
572                        metaData = ((MetaData)pe).getMetaData();
573                    }
574
575                    WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(),
576                            workgroupJID, sessionID, msg.getBody(),
577                            msg.getFrom(), metaData);
578
579                    fireInvitationEvent(inv);
580                }
581            }
582        }
583    }
584
585    /**
586     * IQ packet to request joining the workgroup queue.
587     */
588    private class JoinQueuePacket extends IQ {
589
590        private String userID = null;
591        private DataForm form;
592
593        public JoinQueuePacket(String workgroup, Form answerForm, String userID) {
594            this.userID = userID;
595
596            setTo(workgroup);
597            setType(IQ.Type.SET);
598
599            form = answerForm.getDataFormToSend();
600            addExtension(form);
601        }
602
603        public String getChildElementXML() {
604            StringBuilder buf = new StringBuilder();
605
606            buf.append("<join-queue xmlns=\"http://jabber.org/protocol/workgroup\">");
607            buf.append("<queue-notifications/>");
608            // Add the user unique identification if the session is anonymous
609            if (connection.isAnonymous()) {
610                buf.append(new UserID(userID).toXML());
611            }
612
613            // Append data form text
614            buf.append(form.toXML());
615
616            buf.append("</join-queue>");
617
618            return buf.toString();
619        }
620    }
621
622    /**
623     * Returns a single chat setting based on it's identified key.
624     *
625     * @param key the key to find.
626     * @return the ChatSetting if found, otherwise false.
627     * @throws XMPPException if an error occurs while getting information from the server.
628     */
629    public ChatSetting getChatSetting(String key) throws XMPPException {
630        ChatSettings chatSettings = getChatSettings(key, -1);
631        return chatSettings.getFirstEntry();
632    }
633
634    /**
635     * Returns ChatSettings based on type.
636     *
637     * @param type the type of ChatSettings to return.
638     * @return the ChatSettings of given type, otherwise null.
639     * @throws XMPPException if an error occurs while getting information from the server.
640     */
641    public ChatSettings getChatSettings(int type) throws XMPPException {
642        return getChatSettings(null, type);
643    }
644
645    /**
646     * Returns all ChatSettings.
647     *
648     * @return all ChatSettings of a given workgroup.
649     * @throws XMPPException if an error occurs while getting information from the server.
650     */
651    public ChatSettings getChatSettings() throws XMPPException {
652        return getChatSettings(null, -1);
653    }
654
655
656    /**
657     * Asks the workgroup for it's Chat Settings.
658     *
659     * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null.
660     * @throws XMPPException if an error occurs while getting information from the server.
661     */
662    private ChatSettings getChatSettings(String key, int type) throws XMPPException {
663        ChatSettings request = new ChatSettings();
664        if (key != null) {
665            request.setKey(key);
666        }
667        if (type != -1) {
668            request.setType(type);
669        }
670        request.setType(IQ.Type.GET);
671        request.setTo(workgroupJID);
672
673        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
674        connection.sendPacket(request);
675
676
677        ChatSettings response = (ChatSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
678
679        // Cancel the collector.
680        collector.cancel();
681        if (response == null) {
682            throw new XMPPException("No response from server.");
683        }
684        if (response.getError() != null) {
685            throw new XMPPException(response.getError());
686        }
687        return response;
688    }
689
690    /**
691     * The workgroup service may be configured to send email. This queries the Workgroup Service
692     * to see if the email service has been configured and is available.
693     *
694     * @return true if the email service is available, otherwise return false.
695     */
696    public boolean isEmailAvailable() {
697        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
698
699        try {
700            String workgroupService = StringUtils.parseServer(workgroupJID);
701            DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService);
702            return infoResult.containsFeature("jive:email:provider");
703        }
704        catch (XMPPException e) {
705            return false;
706        }
707    }
708
709    /**
710     * Asks the workgroup for it's Offline Settings.
711     *
712     * @return offlineSettings the offline settings for this workgroup.
713     * @throws XMPPException if an error occurs while getting information from the server.
714     */
715    public OfflineSettings getOfflineSettings() throws XMPPException {
716        OfflineSettings request = new OfflineSettings();
717        request.setType(IQ.Type.GET);
718        request.setTo(workgroupJID);
719
720        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
721        connection.sendPacket(request);
722
723
724        OfflineSettings response = (OfflineSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
725
726        // Cancel the collector.
727        collector.cancel();
728        if (response == null) {
729            throw new XMPPException("No response from server.");
730        }
731        if (response.getError() != null) {
732            throw new XMPPException(response.getError());
733        }
734        return response;
735    }
736
737    /**
738     * Asks the workgroup for it's Sound Settings.
739     *
740     * @return soundSettings the sound settings for the specified workgroup.
741     * @throws XMPPException if an error occurs while getting information from the server.
742     */
743    public SoundSettings getSoundSettings() throws XMPPException {
744        SoundSettings request = new SoundSettings();
745        request.setType(IQ.Type.GET);
746        request.setTo(workgroupJID);
747
748        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
749        connection.sendPacket(request);
750
751
752        SoundSettings response = (SoundSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
753
754        // Cancel the collector.
755        collector.cancel();
756        if (response == null) {
757            throw new XMPPException("No response from server.");
758        }
759        if (response.getError() != null) {
760            throw new XMPPException(response.getError());
761        }
762        return response;
763    }
764
765    /**
766     * Asks the workgroup for it's Properties
767     *
768     * @return the WorkgroupProperties for the specified workgroup.
769     * @throws XMPPException if an error occurs while getting information from the server.
770     */
771    public WorkgroupProperties getWorkgroupProperties() throws XMPPException {
772        WorkgroupProperties request = new WorkgroupProperties();
773        request.setType(IQ.Type.GET);
774        request.setTo(workgroupJID);
775
776        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
777        connection.sendPacket(request);
778
779
780        WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
781
782        // Cancel the collector.
783        collector.cancel();
784        if (response == null) {
785            throw new XMPPException("No response from server.");
786        }
787        if (response.getError() != null) {
788            throw new XMPPException(response.getError());
789        }
790        return response;
791    }
792
793    /**
794     * Asks the workgroup for it's Properties
795     *
796     * @param jid the jid of the user who's information you would like the workgroup to retreive.
797     * @return the WorkgroupProperties for the specified workgroup.
798     * @throws XMPPException if an error occurs while getting information from the server.
799     */
800    public WorkgroupProperties getWorkgroupProperties(String jid) throws XMPPException {
801        WorkgroupProperties request = new WorkgroupProperties();
802        request.setJid(jid);
803        request.setType(IQ.Type.GET);
804        request.setTo(workgroupJID);
805
806        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
807        connection.sendPacket(request);
808
809
810        WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
811
812        // Cancel the collector.
813        collector.cancel();
814        if (response == null) {
815            throw new XMPPException("No response from server.");
816        }
817        if (response.getError() != null) {
818            throw new XMPPException(response.getError());
819        }
820        return response;
821    }
822
823
824    /**
825     * Returns the Form to use for all clients of a workgroup. It is unlikely that the server
826     * will change the form (without a restart) so it is safe to keep the returned form
827     * for future submissions.
828     *
829     * @return the Form to use for searching transcripts.
830     * @throws XMPPException if an error occurs while sending the request to the server.
831     */
832    public Form getWorkgroupForm() throws XMPPException {
833        WorkgroupForm workgroupForm = new WorkgroupForm();
834        workgroupForm.setType(IQ.Type.GET);
835        workgroupForm.setTo(workgroupJID);
836
837        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(workgroupForm.getPacketID()));
838        connection.sendPacket(workgroupForm);
839
840        WorkgroupForm response = (WorkgroupForm)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
841
842        // Cancel the collector.
843        collector.cancel();
844        if (response == null) {
845            throw new XMPPException("No response from server on status set.");
846        }
847        if (response.getError() != null) {
848            throw new XMPPException(response.getError());
849        }
850        return Form.getFormFrom(response);
851    }
852
853    /*
854    public static void main(String args[]) throws Exception {
855        Connection con = new XMPPConnection("anteros");
856        con.connect();
857        con.loginAnonymously();
858
859        Workgroup workgroup = new Workgroup("demo@workgroup.anteros", con);
860        WorkgroupProperties props = workgroup.getWorkgroupProperties("derek@anteros.com");
861
862        System.out.print(props);
863        con.disconnect();
864    }
865    */
866
867
868}