001/**
002 *
003 * Copyright 2016 Florian Schmaus
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.smack.roster;
018
019import java.util.Collection;
020import java.util.Date;
021import java.util.concurrent.TimeoutException;
022import java.util.concurrent.locks.Condition;
023import java.util.concurrent.locks.Lock;
024import java.util.concurrent.locks.ReentrantLock;
025
026import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
027import org.jivesoftware.smack.SmackException.NotConnectedException;
028import org.jivesoftware.smack.SmackException.NotLoggedInException;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.packet.Presence;
031
032import org.jxmpp.jid.BareJid;
033import org.jxmpp.jid.Jid;
034
035public class RosterUtil {
036
037    public static void waitUntilOtherEntityIsSubscribed(Roster roster, BareJid otherEntity, long timeoutMillis)
038                    throws InterruptedException, TimeoutException {
039        Date deadline = new Date(System.currentTimeMillis() + timeoutMillis);
040        waitUntilOtherEntityIsSubscribed(roster, otherEntity, deadline);
041    }
042
043    public static void waitUntilOtherEntityIsSubscribed(Roster roster, final BareJid otherEntity, Date deadline)
044                    throws InterruptedException, TimeoutException {
045        final Lock lock = new ReentrantLock();
046        final Condition maybeSubscribed = lock.newCondition();
047        RosterListener rosterListener = new AbstractRosterListener() {
048            private void signal() {
049                lock.lock();
050                try {
051                    // No need to use signalAll() here.
052                    maybeSubscribed.signal();
053                }
054                finally {
055                    lock.unlock();
056                }
057            }
058
059            @Override
060            public void entriesAdded(Collection<Jid> addresses) {
061                signal();
062            }
063
064            @Override
065            public void entriesUpdated(Collection<Jid> addresses) {
066                signal();
067            }
068        };
069
070        roster.addRosterListener(rosterListener);
071
072        boolean stillWaiting = true;
073        // Using the example code pattern from Condition.awaitUntil(Date) javadoc.
074        lock.lock();
075        try {
076            while (!roster.isSubscribedToMyPresence(otherEntity)) {
077                if (!stillWaiting) {
078                    throw new TimeoutException();
079                }
080                stillWaiting = maybeSubscribed.awaitUntil(deadline);
081            }
082        }
083        finally {
084            lock.unlock();
085            // Make sure the listener is removed, so we don't leak it.
086            roster.removeRosterListener(rosterListener);
087        }
088    }
089
090    /**
091     * Pre-approve the subscription if it is required and possible.
092     *
093     * @param roster The roster which should be used for the pre-approval.
094     * @param jid The XMPP address which should be pre-approved.
095     * @throws NotLoggedInException if the XMPP connection is not authenticated.
096     * @throws NotConnectedException if the XMPP connection is not connected.
097     * @throws InterruptedException if the calling thread was interrupted.
098     * @since 4.2.2
099     */
100    public static void preApproveSubscriptionIfRequiredAndPossible(Roster roster, BareJid jid)
101            throws NotLoggedInException, NotConnectedException, InterruptedException {
102        if (!roster.isSubscriptionPreApprovalSupported()) {
103            return;
104        }
105
106        RosterEntry entry = roster.getEntry(jid);
107        if (entry == null || (!entry.canSeeMyPresence() && !entry.isApproved())) {
108            try {
109                roster.preApprove(jid);
110            } catch (FeatureNotSupportedException e) {
111                // Should never happen since we checked for the feature above.
112                throw new AssertionError(e);
113            }
114        }
115    }
116
117    public static void askForSubscriptionIfRequired(Roster roster, BareJid jid)
118            throws NotLoggedInException, NotConnectedException, InterruptedException {
119        RosterEntry entry = roster.getEntry(jid);
120        if (entry == null || !(entry.canSeeHisPresence() || entry.isSubscriptionPending())) {
121            roster.sendSubscriptionRequest(jid);
122        }
123    }
124
125    public static void ensureNotSubscribedToEachOther(XMPPConnection connectionOne, XMPPConnection connectionTwo)
126            throws NotConnectedException, InterruptedException {
127        final Roster rosterOne = Roster.getInstanceFor(connectionOne);
128        final BareJid jidOne = connectionOne.getUser().asBareJid();
129        final Roster rosterTwo = Roster.getInstanceFor(connectionTwo);
130        final BareJid jidTwo = connectionTwo.getUser().asBareJid();
131
132        ensureNotSubscribed(rosterOne, jidTwo);
133        ensureNotSubscribed(rosterTwo, jidOne);
134    }
135
136    public static void ensureNotSubscribed(Roster roster, BareJid jid)
137            throws NotConnectedException, InterruptedException {
138        RosterEntry entry = roster.getEntry(jid);
139        if (entry != null && entry.canSeeMyPresence()) {
140            entry.cancelSubscription();
141        }
142    }
143
144    public static void ensureSubscribed(XMPPConnection connectionOne, XMPPConnection connectionTwo, long timeout)
145                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
146        ensureSubscribedTo(connectionOne, connectionTwo, timeout);
147        ensureSubscribedTo(connectionTwo, connectionOne, timeout);
148    }
149
150    public static void ensureSubscribedTo(XMPPConnection connectionOne, XMPPConnection connectionTwo, long timeout)
151                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
152        Date deadline = new Date(System.currentTimeMillis() + timeout);
153        ensureSubscribedTo(connectionOne, connectionTwo, deadline);
154    }
155
156    public static void ensureSubscribedTo(final XMPPConnection connectionOne, final XMPPConnection connectionTwo,
157                    final Date deadline)
158                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
159        final Roster rosterOne = Roster.getInstanceFor(connectionOne);
160        final BareJid jidTwo = connectionTwo.getUser().asBareJid();
161
162        if (rosterOne.iAmSubscribedTo(jidTwo))
163            return;
164
165        final BareJid jidOne = connectionOne.getUser().asBareJid();
166        final SubscribeListener subscribeListener = new SubscribeListener() {
167            @Override
168            public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) {
169                if (from.equals(jidOne)) {
170                    return SubscribeAnswer.Approve;
171                }
172                return null;
173            }
174        };
175        final Roster rosterTwo = Roster.getInstanceFor(connectionTwo);
176
177        rosterTwo.addSubscribeListener(subscribeListener);
178        try {
179            rosterOne.sendSubscriptionRequest(jidTwo);
180            waitUntilOtherEntityIsSubscribed(rosterTwo, jidOne, deadline);
181        }
182        finally {
183            rosterTwo.removeSubscribeListener(subscribeListener);
184        }
185    }
186}