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