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