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