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.NotConnectedException;
027import org.jivesoftware.smack.SmackException.NotLoggedInException;
028import org.jivesoftware.smack.XMPPConnection;
029import org.jivesoftware.smack.packet.Presence;
030import org.jxmpp.jid.BareJid;
031import org.jxmpp.jid.Jid;
032
033public class RosterUtil {
034
035    public static void waitUntilOtherEntityIsSubscribed(Roster roster, BareJid otherEntity, long timeoutMillis)
036                    throws InterruptedException, TimeoutException {
037        Date deadline = new Date(System.currentTimeMillis() + timeoutMillis);
038        waitUntilOtherEntityIsSubscribed(roster, otherEntity, deadline);
039    }
040
041    public static void waitUntilOtherEntityIsSubscribed(Roster roster, final BareJid otherEntity, Date deadline)
042                    throws InterruptedException, TimeoutException {
043        final Lock lock = new ReentrantLock();
044        final Condition maybeSubscribed = lock.newCondition();
045        RosterListener rosterListener = new AbstractRosterListener() {
046            private void signal() {
047                lock.lock();
048                try {
049                    // No need to use signalAll() here.
050                    maybeSubscribed.signal();
051                }
052                finally {
053                    lock.unlock();
054                }
055            }
056
057            @Override
058            public void entriesAdded(Collection<Jid> addresses) {
059                signal();
060            }
061
062            @Override
063            public void entriesUpdated(Collection<Jid> addresses) {
064                signal();
065            }
066        };
067
068        roster.addRosterListener(rosterListener);
069
070        boolean stillWaiting = true;
071        // Using the example code pattern from Condition.awaitUntil(Date) javadoc.
072        lock.lock();
073        try {
074            while (!roster.isSubscribedToMyPresence(otherEntity)) {
075                if (!stillWaiting) {
076                    throw new TimeoutException();
077                }
078                stillWaiting = maybeSubscribed.awaitUntil(deadline);
079            }
080        }
081        finally {
082            lock.unlock();
083            // Make sure the listener is removed, so we don't leak it.
084            roster.removeRosterListener(rosterListener);
085        }
086    }
087
088    public static void askForSubscriptionIfRequired(Roster roster, BareJid jid)
089            throws NotLoggedInException, NotConnectedException, InterruptedException {
090        RosterEntry entry = roster.getEntry(jid);
091        if (entry == null || !(entry.canSeeHisPresence() || entry.isSubscriptionPending())) {
092            roster.sendSubscriptionRequest(jid);
093        }
094    }
095
096    public static void ensureNotSubscribedToEachOther(XMPPConnection connectionOne, XMPPConnection connectionTwo)
097            throws NotConnectedException, InterruptedException {
098        final Roster rosterOne = Roster.getInstanceFor(connectionOne);
099        final BareJid jidOne = connectionOne.getUser().asBareJid();
100        final Roster rosterTwo = Roster.getInstanceFor(connectionTwo);
101        final BareJid jidTwo = connectionTwo.getUser().asBareJid();
102
103        ensureNotSubscribed(rosterOne, jidTwo);
104        ensureNotSubscribed(rosterTwo, jidOne);
105    }
106
107    public static void ensureNotSubscribed(Roster roster, BareJid jid)
108            throws NotConnectedException, InterruptedException {
109        RosterEntry entry = roster.getEntry(jid);
110        if (entry != null && entry.canSeeMyPresence()) {
111            entry.cancelSubscription();
112        }
113    }
114
115    public static void ensureSubscribed(XMPPConnection connectionOne, XMPPConnection connectionTwo, long timeout)
116                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
117        ensureSubscribedTo(connectionOne, connectionTwo, timeout);
118        ensureSubscribedTo(connectionTwo, connectionOne, timeout);
119    }
120
121    public static void ensureSubscribedTo(XMPPConnection connectionOne, XMPPConnection connectionTwo, long timeout)
122                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
123        Date deadline = new Date(System.currentTimeMillis() + timeout);
124        ensureSubscribedTo(connectionOne, connectionTwo, deadline);
125    }
126
127    public static void ensureSubscribedTo(final XMPPConnection connectionOne, final XMPPConnection connectionTwo,
128                    final Date deadline)
129                    throws NotLoggedInException, NotConnectedException, InterruptedException, TimeoutException {
130        final Roster rosterOne = Roster.getInstanceFor(connectionOne);
131        final BareJid jidTwo = connectionTwo.getUser().asBareJid();
132
133        if (rosterOne.iAmSubscribedTo(jidTwo))
134            return;
135
136        final BareJid jidOne = connectionOne.getUser().asBareJid();
137        final SubscribeListener subscribeListener = new SubscribeListener() {
138            @Override
139            public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) {
140                if (from.equals(jidOne)) {
141                    return SubscribeAnswer.Approve;
142                }
143                return null;
144            }
145        };
146        final Roster rosterTwo = Roster.getInstanceFor(connectionTwo);
147
148        rosterTwo.addSubscribeListener(subscribeListener);
149        try {
150            rosterOne.sendSubscriptionRequest(jidTwo);
151            waitUntilOtherEntityIsSubscribed(rosterTwo, jidOne, deadline);
152        }
153        finally {
154            rosterTwo.removeSubscribeListener(subscribeListener);
155        }
156    }
157}