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.FormField;
060import org.jivesoftware.smackx.xdata.TextSingleFormField;
061import org.jivesoftware.smackx.xdata.form.FillableForm;
062import org.jivesoftware.smackx.xdata.form.Form;
063import org.jivesoftware.smackx.xdata.packet.DataForm;
064
065import org.jxmpp.jid.DomainBareJid;
066import org.jxmpp.jid.EntityBareJid;
067import org.jxmpp.jid.EntityJid;
068import org.jxmpp.jid.Jid;
069
070/**
071 * Provides workgroup services for users. Users can join the workgroup queue, depart the
072 * queue, find status information about their placement in the queue, and register to
073 * be notified when they are routed to an agent.<p>
074 *
075 * This class only provides a users perspective into a workgroup and is not intended
076 * for use by agents.
077 *
078 * @author Matt Tucker
079 * @author Derek DeMoro
080 */
081public class Workgroup {
082
083    private final EntityBareJid workgroupJID;
084    private final XMPPConnection connection;
085    private boolean inQueue;
086    private final CopyOnWriteArraySet<WorkgroupInvitationListener> invitationListeners;
087    private final CopyOnWriteArraySet<QueueListener> queueListeners;
088
089    private int queuePosition = -1;
090    private int queueRemainingTime = -1;
091
092    /**
093     * Creates a new workgroup instance using the specified workgroup JID
094     * (eg support@workgroup.example.com) and XMPP connection. The connection must have
095     * undergone a successful login before being used to construct an instance of
096     * this class.
097     *
098     * @param workgroupJID the JID of the workgroup.
099     * @param connection   an XMPP connection which must have already undergone a
100     *                     successful login.
101     */
102    public Workgroup(EntityBareJid workgroupJID, XMPPConnection connection) {
103        // Login must have been done before passing in connection.
104        if (!connection.isAuthenticated()) {
105            throw new IllegalStateException("Must login to server before creating workgroup.");
106        }
107
108        this.workgroupJID = workgroupJID;
109        this.connection = connection;
110        inQueue = false;
111        invitationListeners = new CopyOnWriteArraySet<>();
112        queueListeners = new CopyOnWriteArraySet<>();
113
114        // Register as a queue listener for internal usage by this instance.
115        addQueueListener(new QueueListener() {
116            @Override
117            public void joinedQueue() {
118                inQueue = true;
119            }
120
121            @Override
122            public void departedQueue() {
123                inQueue = false;
124                queuePosition = -1;
125                queueRemainingTime = -1;
126            }
127
128            @Override
129            public void queuePositionUpdated(int currentPosition) {
130                queuePosition = currentPosition;
131            }
132
133            @Override
134            public void queueWaitTimeUpdated(int secondsRemaining) {
135                queueRemainingTime = secondsRemaining;
136            }
137        });
138
139        /**
140         * Internal handling of an invitation.Recieving an invitation removes the user from the queue.
141         */
142        MultiUserChatManager.getInstanceFor(connection).addInvitationListener(
143                new org.jivesoftware.smackx.muc.InvitationListener() {
144                    @Override
145                    public void invitationReceived(XMPPConnection conn, org.jivesoftware.smackx.muc.MultiUserChat room, EntityJid inviter,
146                                                   String reason, String password, Message message, MUCUser.Invite invitation) {
147                        inQueue = false;
148                        queuePosition = -1;
149                        queueRemainingTime = -1;
150                    }
151                });
152
153        // Register a packet listener for all the messages sent to this client.
154        StanzaFilter typeFilter = new StanzaTypeFilter(Message.class);
155
156        connection.addAsyncStanzaListener(new StanzaListener() {
157            @Override
158            public void processStanza(Stanza packet) {
159                handlePacket(packet);
160            }
161        }, typeFilter);
162    }
163
164    /**
165     * Returns the name of this workgroup (eg support@example.com).
166     *
167     * @return the name of the workgroup.
168     */
169    public EntityBareJid getWorkgroupJID() {
170        return workgroupJID;
171    }
172
173    /**
174     * Returns true if the user is currently waiting in the workgroup queue.
175     *
176     * @return true if currently waiting in the queue.
177     */
178    public boolean isInQueue() {
179        return inQueue;
180    }
181
182    /**
183     * Returns true if the workgroup is available for receiving new requests. The workgroup will be
184     * available only when agents are available for this workgroup.
185     *
186     * @return true if the workgroup is available for receiving new requests.
187     * @throws XMPPErrorException if there was an XMPP error returned.
188     * @throws NoResponseException if there was no response from the remote entity.
189     * @throws NotConnectedException if the XMPP connection is not connected.
190     * @throws InterruptedException if the calling thread was interrupted.
191     */
192    public boolean isAvailable() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
193        Presence directedPresence = connection.getStanzaFactory().buildPresenceStanza()
194                .ofType(Presence.Type.available)
195                .to(workgroupJID)
196                .build();
197
198        StanzaFilter typeFilter = new StanzaTypeFilter(Presence.class);
199        StanzaFilter fromFilter = FromMatchesFilter.create(workgroupJID);
200        StanzaCollector collector = connection.createStanzaCollectorAndSend(new AndFilter(fromFilter,
201                typeFilter), directedPresence);
202
203        Presence response = collector.nextResultOrThrow();
204        return Presence.Type.available == response.getType();
205    }
206
207    /**
208     * Returns the users current position in the workgroup queue. A value of 0 means
209     * the user is next in line to be routed; therefore, if the queue position
210     * is being displayed to the end user it is usually a good idea to add 1 to
211     * the value this method returns before display. If the user is not currently
212     * waiting in the workgroup, or no queue position information is available, -1
213     * will be returned.
214     *
215     * @return the user's current position in the workgroup queue, or -1 if the
216     *         position isn't available or if the user isn't in the queue.
217     */
218    public int getQueuePosition() {
219        return queuePosition;
220    }
221
222    /**
223     * Returns the estimated time (in seconds) that the user has to left wait in
224     * the workgroup queue before being routed. If the user is not currently waiting
225     * int he workgroup, or no queue time information is available, -1 will be
226     * returned.
227     *
228     * @return the estimated time remaining (in seconds) that the user has to
229     *         wait in the workgroup queue, or -1 if time information isn't available
230     *         or if the user isn't int the queue.
231     */
232    public int getQueueRemainingTime() {
233        return queueRemainingTime;
234    }
235
236    /**
237     * Joins the workgroup queue to wait to be routed to an agent. After joining
238     * the queue, queue status events will be sent to indicate the user's position and
239     * estimated time left in the queue. Once joining the queue, there are three ways
240     * the user can leave the queue: <ul>
241     *
242     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
243     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
244     * <li>A server error occurs, or an administrator explicitly removes the user
245     * from the queue.
246     * </ul>
247     *
248     * A user cannot request to join the queue again if already in the queue. Therefore,
249     * this method will throw an IllegalStateException if the user is already in the queue.<p>
250     *
251     * Some servers may be configured to require certain meta-data in order to
252     * join the queue. In that case, the {@link #joinQueue(FillableForm)} method should be
253     * used instead of this method so that meta-data may be passed in.<p>
254     *
255     * The server tracks the conversations that a user has with agents over time. By
256     * default, that tracking is done using the user's JID. However, this is not always
257     * possible. For example, when the user is logged in anonymously using a web client.
258     * In that case the user ID might be a randomly generated value put into a persistent
259     * cookie or a username obtained via the session. A userID can be explicitly
260     * passed in by using the {@link #joinQueue(DataForm, Jid)} method. When specified,
261     * that userID will be used instead of the user's JID to track conversations. The
262     * server will ignore a manually specified userID if the user's connection to the server
263     * is not anonymous.
264     *
265     * @throws XMPPException if an error occurred joining the queue. An error may indicate
266     *                       that a connection failure occurred or that the server explicitly rejected the
267     *                       request to join the queue.
268     * @throws SmackException if Smack detected an exceptional situation.
269     * @throws InterruptedException if the calling thread was interrupted.
270     */
271    public void joinQueue() throws XMPPException, SmackException, InterruptedException {
272        joinQueue(null);
273    }
274
275    /**
276     * Joins the workgroup queue to wait to be routed to an agent. After joining
277     * the queue, queue status events will be sent to indicate the user's position and
278     * estimated time left in the queue. Once joining the queue, there are three ways
279     * the user can leave the queue: <ul>
280     *
281     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
282     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
283     * <li>A server error occurs, or an administrator explicitly removes the user
284     * from the queue.
285     * </ul>
286     *
287     * A user cannot request to join the queue again if already in the queue. Therefore,
288     * this method will throw an IllegalStateException if the user is already in the queue.<p>
289     *
290     * Some servers may be configured to require certain meta-data in order to
291     * join the queue.<p>
292     *
293     * The server tracks the conversations that a user has with agents over time. By
294     * default, that tracking is done using the user's JID. However, this is not always
295     * possible. For example, when the user is logged in anonymously using a web client.
296     * In that case the user ID might be a randomly generated value put into a persistent
297     * cookie or a username obtained via the session. A userID can be explicitly
298     * passed in by using the {@link #joinQueue(DataForm, Jid)} method. When specified,
299     * that userID will be used instead of the user's JID to track conversations. The
300     * server will ignore a manually specified userID if the user's connection to the server
301     * is not anonymous.
302     *
303     * @param answerForm the completed form the send for the join request.
304     * @throws XMPPException if an error occurred joining the queue. An error may indicate
305     *                       that a connection failure occurred or that the server explicitly rejected the
306     *                       request to join the queue.
307     * @throws SmackException if Smack detected an exceptional situation.
308     * @throws InterruptedException if the calling thread was interrupted.
309     */
310    public void joinQueue(FillableForm answerForm) throws XMPPException, SmackException, InterruptedException {
311        joinQueue(answerForm.getDataFormToSubmit(), null);
312    }
313
314    /**
315     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
316     * the queue, queue status events will be sent to indicate the user's position and
317     * estimated time left in the queue. Once joining the queue, there are three ways
318     * the user can leave the queue: <ul>
319     *
320     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
321     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
322     * <li>A server error occurs, or an administrator explicitly removes the user
323     * from the queue.
324     * </ul>
325     *
326     * A user cannot request to join the queue again if already in the queue. Therefore,
327     * this method will throw an IllegalStateException if the user is already in the queue.<p>
328     *
329     * Some servers may be configured to require certain meta-data in order to
330     * join the queue.<p>
331     *
332     * The server tracks the conversations that a user has with agents over time. By
333     * default, that tracking is done using the user's JID. However, this is not always
334     * possible. For example, when the user is logged in anonymously using a web client.
335     * In that case the user ID might be a randomly generated value put into a persistent
336     * cookie or a username obtained via the session. When specified, that userID will
337     * be used instead of the user's JID to track conversations. The server will ignore a
338     * manually specified userID if the user's connection to the server is not anonymous.
339     *
340     * @param answerForm the completed form associated with the join request.
341     * @param userID     String that represents the ID of the user when using anonymous sessions
342     *                   or <code>null</code> if a userID should not be used.
343     * @throws XMPPErrorException if an error occurred joining the queue. An error may indicate
344     *                       that a connection failure occurred or that the server explicitly rejected the
345     *                       request to join the queue.
346     * @throws NoResponseException if there was no response from the remote entity.
347     * @throws NotConnectedException if the XMPP connection is not connected.
348     * @throws InterruptedException if the calling thread was interrupted.
349     */
350    public void joinQueue(DataForm answerForm, Jid userID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
351        // If already in the queue ignore the join request.
352        if (inQueue) {
353            throw new IllegalStateException("Already in queue " + workgroupJID);
354        }
355
356        JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID);
357
358        connection.createStanzaCollectorAndSend(joinPacket).nextResultOrThrow();
359        // Notify listeners that we've joined the queue.
360        fireQueueJoinedEvent();
361    }
362
363    /**
364     * <p>Joins the workgroup queue to wait to be routed to an agent. After joining
365     * the queue, queue status events will be sent to indicate the user's position and
366     * estimated time left in the queue. Once joining the queue, there are three ways
367     * the user can leave the queue: <ul>
368     *
369     * <li>The user is routed to an agent, which triggers a GroupChat invitation.
370     * <li>The user asks to leave the queue by calling the {@link #departQueue} method.
371     * <li>A server error occurs, or an administrator explicitly removes the user
372     * from the queue.
373     * </ul>
374     *
375     * A user cannot request to join the queue again if already in the queue. Therefore,
376     * this method will throw an IllegalStateException if the user is already in the queue.<p>
377     *
378     * Some servers may be configured to require certain meta-data in order to
379     * join the queue.<p>
380     *
381     * The server tracks the conversations that a user has with agents over time. By
382     * default, that tracking is done using the user's JID. However, this is not always
383     * possible. For example, when the user is logged in anonymously using a web client.
384     * In that case the user ID might be a randomly generated value put into a persistent
385     * cookie or a username obtained via the session. When specified, that userID will
386     * be used instead of the user's JID to track conversations. The server will ignore a
387     * manually specified userID if the user's connection to the server is not anonymous.
388     *
389     * @param metadata metadata to create a dataform from.
390     * @param userID   String that represents the ID of the user when using anonymous sessions
391     *                 or <code>null</code> if a userID should not be used.
392     * @throws XMPPException if an error occurred joining the queue. An error may indicate
393     *                       that a connection failure occurred or that the server explicitly rejected the
394     *                       request to join the queue.
395     * @throws SmackException if Smack detected an exceptional situation.
396     * @throws InterruptedException if the calling thread was interrupted.
397     */
398    public void joinQueue(Map<String, Object> metadata, Jid userID) throws XMPPException, SmackException, InterruptedException {
399        // If already in the queue ignore the join request.
400        if (inQueue) {
401            throw new IllegalStateException("Already in queue " + workgroupJID);
402        }
403
404        // Build dataform from metadata
405        DataForm.Builder form = DataForm.builder();
406        Iterator<String> iter = metadata.keySet().iterator();
407        while (iter.hasNext()) {
408            String name = iter.next();
409            String value = metadata.get(name).toString();
410
411            TextSingleFormField.Builder field = FormField.builder(name);
412            field.setValue(value);
413            form.addField(field.build());
414        }
415        joinQueue(form.build(), userID);
416    }
417
418    /**
419     * Departs the workgroup queue. If the user is not currently in the queue, this
420     * method will do nothing.<p>
421     *
422     * Normally, the user would not manually leave the queue. However, they may wish to
423     * under certain circumstances -- for example, if they no longer wish to be routed
424     * to an agent because they've been waiting too long.
425     *
426     * @throws XMPPErrorException if an error occurred trying to send the depart queue
427     *                       request to the server.
428     * @throws NoResponseException if there was no response from the remote entity.
429     * @throws NotConnectedException if the XMPP connection is not connected.
430     * @throws InterruptedException if the calling thread was interrupted.
431     */
432    public void departQueue() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
433        // If not in the queue ignore the depart request.
434        if (!inQueue) {
435            return;
436        }
437
438        DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID);
439        connection.createStanzaCollectorAndSend(departPacket).nextResultOrThrow();
440
441        // Notify listeners that we're no longer in the queue.
442        fireQueueDepartedEvent();
443    }
444
445    /**
446     * Adds a queue listener that will be notified of queue events for the user
447     * that created this Workgroup instance.
448     *
449     * @param queueListener the queue listener.
450     */
451    public void addQueueListener(QueueListener queueListener) {
452        queueListeners.add(queueListener);
453    }
454
455    /**
456     * Removes a queue listener.
457     *
458     * @param queueListener the queue listener.
459     */
460    public void removeQueueListener(QueueListener queueListener) {
461        queueListeners.remove(queueListener);
462    }
463
464    /**
465     * Adds an invitation listener that will be notified of groupchat invitations
466     * from the workgroup for the the user that created this Workgroup instance.
467     *
468     * @param invitationListener the invitation listener.
469     */
470    public void addInvitationListener(WorkgroupInvitationListener invitationListener) {
471        invitationListeners.add(invitationListener);
472    }
473
474    /**
475     * Removes an invitation listener.
476     *
477     * @param invitationListener the invitation listener.
478     */
479    public void removeQueueListener(WorkgroupInvitationListener invitationListener) {
480        invitationListeners.remove(invitationListener);
481    }
482
483    private void fireInvitationEvent(WorkgroupInvitation invitation) {
484        for (WorkgroupInvitationListener listener : invitationListeners) {
485            listener.invitationReceived(invitation);
486        }
487    }
488
489    private void fireQueueJoinedEvent() {
490        for (QueueListener listener : queueListeners) {
491            listener.joinedQueue();
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.getExtensionElement("depart-queue", "http://jabber.org/protocol/workgroup");
520            ExtensionElement queueStatus = msg.getExtensionElement("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 = MUCUser.from(msg);
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.getExtensionElement(SessionID.ELEMENT_NAME,
544                            SessionID.NAMESPACE);
545                    if (pe != null) {
546                        sessionID = ((SessionID) pe).getSessionID();
547                    }
548
549                    pe = msg.getExtensionElement(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, DataForm 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;
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());
591            }
592
593            // Append data form text
594            buf.append(form.toXML());
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 if Smack detected an exceptional situation.
607     * @throws InterruptedException if the calling thread was interrupted.
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 if Smack detected an exceptional situation.
621     * @throws InterruptedException if the calling thread was interrupted.
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 if Smack detected an exceptional situation.
633     * @throws InterruptedException if the calling thread was interrupted.
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 if there was no response from the remote entity.
645     * @throws XMPPErrorException if an error occurs while getting information from the server.
646     * @throws NotConnectedException if the XMPP connection is not connected.
647     * @throws InterruptedException if the calling thread was interrupted.
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 if Smack detected an exceptional situation.
671     * @throws InterruptedException if the calling thread was interrupted.
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 if there was an XMPP error returned.
691     * @throws NoResponseException if there was no response from the remote entity.
692     * @throws NotConnectedException if the XMPP connection is not connected.
693     * @throws InterruptedException if the calling thread was interrupted.
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 if there was an XMPP error returned.
708     * @throws NoResponseException if there was no response from the remote entity.
709     * @throws NotConnectedException if the XMPP connection is not connected.
710     * @throws InterruptedException if the calling thread was interrupted.
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 if there was an XMPP error returned.
725     * @throws NoResponseException if there was no response from the remote entity.
726     * @throws NotConnectedException if the XMPP connection is not connected.
727     * @throws InterruptedException if the calling thread was interrupted.
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 if there was an XMPP error returned.
743     * @throws NoResponseException if there was no response from the remote entity.
744     * @throws NotConnectedException if the XMPP connection is not connected.
745     * @throws InterruptedException if the calling thread was interrupted.
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 if there was an XMPP error returned.
765     * @throws NoResponseException if there was no response from the remote entity.
766     * @throws NotConnectedException if the XMPP connection is not connected.
767     * @throws InterruptedException if the calling thread was interrupted.
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.from(response);
777    }
778}