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}