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}