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