/** * Discovers the capabilities of another XMPP entity. * * @param jid The JID, which should usually be a full JID. * @return The async result with the capabilities in form of a info node, which contains the identities, the features and service discovery extensions. * @see <a href="https://xmpp.org/extensions/xep-0115.html#discover">6.2 Discovering Capabilities</a> */ public final AsyncResult<InfoNode> discoverCapabilities(Jid jid) { InfoNode infoNode = ENTITY_CAPABILITIES.get(jid); if (infoNode == null) { // Make sure, that for the same JID no multiple concurrent queries are sent. One is enough. return REQUESTS.computeIfAbsent(jid, key -> serviceDiscoveryManager.discoverInformation(jid) .whenComplete((result, e) -> { if (result != null) { ENTITY_CAPABILITIES.put(jid, result); } REQUESTS.remove(jid); })); } return new AsyncResult<>(CompletableFuture.completedFuture(infoNode)); }
/** * Indicates whether chat state notifications are supported by the peer. * * @param jid The JID. * @return An async result indicating whether chat state notifications are supported. * @see <a href="https://xmpp.org/extensions/xep-0085.html#bizrules-gen">5.1 Generation of Notifications</a> */ public final AsyncResult<Boolean> isSupported(Jid jid) { Boolean supports = contactSupportsChatStateNotifications.get(jid); // If support is unknown, discover it via Service Discovery / Entity Capabilities. if (supports == null) { return xmppSession.isSupported(ChatState.NAMESPACE, jid).thenApply(result -> { contactSupportsChatStateNotifications.put(jid, result); return result; }); } else { // If support is known either via explicit or implicit discovery, return the result. return new AsyncResult<>(CompletableFuture.completedFuture(supports)); } }
/** * Resumes the stream. * * @return The async result, which is done, if either the stream is not resumable, or the server resumed the stream. * @see <a href="https://xmpp.org/extensions/xep-0198.html#resumption">5. Resumption</a> */ public AsyncResult<Boolean> resume() { if (!isResumable()) { return new AsyncResult<>(CompletableFuture.completedFuture(false)); } CompletableFuture<Boolean> future = new CompletableFuture<>(); StreamManagement.Resume resume; synchronized (this) { resumeFuture = future; // The <resume/> element MUST include a 'previd' attribute whose value is the SM-ID of the former stream and // MUST include an 'h' attribute that identifies the sequence number of the last handled stanza // sent over the former stream from the server to the client resume = new StreamManagement.Resume(inboundCount, getStreamManagementId()); } xmppSession.send(resume); return new AsyncResult<>(future); } }
return new AsyncResult<>(CompletableFuture.allOf(completionStages.stream() .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture<?>[]::new)));
private <S extends Stanza, E extends EventObject> AsyncResult<S> sendAndAwait(S stanza, Function<E, S> stanzaMapper, final Predicate<S> filter, Function<S, SendTask<S>> sendFunction, Consumer<Consumer<E>> addListener, Consumer<Consumer<E>> removeListener, Duration timeout) { CompletableFuture<S> completableFuture = new CompletableFuture<>(); final Consumer<E> listener = e -> { S st = stanzaMapper.apply(e); if (filter.test(st)) { if (st.getError() != null) { completableFuture.completeExceptionally(new StanzaErrorException(st)); } completableFuture.complete(st); } }; addListener.accept(listener); SendTask<S> sendTask = sendFunction.apply(stanza); // When the sending failed, immediately complete the future with the exception. sendTask.onFailed((throwable, s) -> completableFuture.completeExceptionally(throwable)); return new AsyncResult<>(completableFuture // When a response has received, mark the requesting stanza as acknowledged. // This is especially important for Bind and Roster IQs, so that they won't be resend after login. .whenComplete((result, e) -> removeFromQueue(sendTask.getStanza())) .applyToEither(CompletionStages.timeoutAfter(timeout.toMillis(), TimeUnit.MILLISECONDS, () -> new NoResponseException("Timeout reached, while waiting on a response for request: " + stanza)), Function.identity())) // When either a timeout happened or response has received, remove the listener. .whenComplete((result, e) -> removeListener.accept(listener)); }
/** * Determines, if in-band registration is supported by the server. * * @return The async result with true, if registration is supported by the server; otherwise false. */ public final AsyncResult<Boolean> isRegistrationSupported() { // server returns a stream header to the client and MAY announce support for in-band registration by including the relevant stream feature. boolean isSupported = xmppSession.getManager(StreamFeaturesManager.class).getFeatures().containsKey(RegisterFeature.class); // Since the stream feature is only optional, discover the server features, too. if (!isSupported) { EntityCapabilitiesManager entityCapabilitiesManager = xmppSession.getManager(EntityCapabilitiesManager.class); return entityCapabilitiesManager.discoverCapabilities(xmppSession.getDomain()) .thenApply(infoNode -> infoNode.getFeatures().contains(Registration.NAMESPACE)); } return new AsyncResult<>(CompletableFuture.completedFuture(true)); }
@Override public final AsyncResult<ByteStreamSession> accept() { xmppSession.send(iq.createResult()); return new AsyncResult<>(CompletableFuture.completedFuture(xmppSession.getManager(InBandByteStreamManager.class).createSession(iq.getFrom(), getSessionId(), blockSize, stanzaType))); }
@Override public AsyncResult<FileTransfer> accept(final IQ iq, String sessionId, FileTransferOffer fileTransferOffer, Object protocol, OutputStream outputStream) { return new AsyncResult<>(CompletableFuture.supplyAsync(() -> { try { URL url = new URL(fileTransferOffer.getName()); URLConnection urlConnection = url.openConnection(); final FileTransfer fileTransfer = new FileTransfer(xmppSession, iq.getId(), urlConnection.getInputStream(), outputStream, fileTransferOffer.getSize()); fileTransfer.addFileTransferStatusListener(new Consumer<FileTransferStatusEvent>() { @Override public void accept(FileTransferStatusEvent e) { if (e.getStatus() == FileTransfer.Status.COMPLETED) { xmppSession.send(iq.createResult()); fileTransfer.removeFileTransferStatusListener(this); } else if (e.getStatus() == FileTransfer.Status.CANCELED || e.getStatus() == FileTransfer.Status.FAILED) { xmppSession.send(iq.createError(Condition.ITEM_NOT_FOUND)); fileTransfer.removeFileTransferStatusListener(this); } } }); return fileTransfer; } catch (IOException e) { throw new CompletionException(e); } })); }
/** * Removes a contact group. If the group has sub groups, all sub groups are removed as well. * All contacts in this group and all sub groups are moved to the parent group (if present) or to no group at all. * * @param contactGroup The contact group. * @return The async result. */ public final AsyncResult<Void> removeContactGroup(ContactGroup contactGroup) { Collection<Contact> allContacts = collectAllContactsInGroup(contactGroup); CompletableFuture<?>[] completableFutures; if (contactGroup.getParentGroup() != null) { completableFutures = allContacts.stream() .map(contact -> updateContact(contact.withGroups(contactGroup.getParentGroup().getFullName())).thenRun(() -> { }).toCompletableFuture()) .toArray(CompletableFuture<?>[]::new); } else { completableFutures = allContacts.stream() .map(contact -> updateContact(contact.withoutGroups()).thenRun(() -> { }).toCompletableFuture()) .toArray(CompletableFuture<?>[]::new); } return new AsyncResult<>(CompletableFuture.allOf(completableFutures)); }
byte[] imageData = loadFromCache(hash); if (imageData != null) { return new AsyncResult<>(CompletableFuture.completedFuture(imageData)); return new AsyncResult<>(CompletableFuture.completedFuture(new byte[0]));
if (host != null) { hosts = new AsyncResult<>(CompletableFuture.completedFuture(Collections.singletonList(host))); } else {
/** * Exits the room with a custom message. * * @param message The exit message. * @return The async result. * @see <a href="https://xmpp.org/extensions/xep-0045.html#exit">7.14 Exiting a Room</a> */ public final AsyncResult<Void> exit(String message) { if (!hasEntered()) { return new AsyncResult<>(CompletableFuture.completedFuture(null)); } // Store the current nick, to determine self-presence (because nick gets null before determining self-presence). final String usedNick = getNick(); return xmppSession.sendAndAwaitPresence(new Presence(roomJid.withResource(usedNick), Presence.Type.UNAVAILABLE, message), presence -> { Jid room = presence.getFrom().asBareJid(); return !presence.isAvailable() && room.equals(roomJid) && isSelfPresence(presence, usedNick); }).handle((result, throwable) -> { userHasExited(); return null; }); }
return new AsyncResult<>(CompletableFuture.completedFuture(null));
return new AsyncResult<>(withFallbackStage.applyToEither(CompletionStages.timeoutAfter(xmppSession.getConfiguration().getDefaultResponseTimeout().toMillis() * 5, TimeUnit.MILLISECONDS), byteStreamSession -> { try { return new FileTransfer(xmppSession, byteStreamSession.getSessionId(), byteStreamSession.getInputStream(), outputStream, fileTransferOffer.getSize());
@Override public final AsyncResult<ByteStreamSession> accept() { CompletableFuture<ByteStreamSession> completableFuture = CompletableFuture.supplyAsync(() -> { try { // 5.3.2 Target Establishes SOCKS5 Connection with StreamHost/Requester // 6.3.2 Target Establishes SOCKS5 Connection with Proxy S5bSession s5bSession = Socks5ByteStreamManager.createS5bSession(iq.getFrom(), iq.getTo(), getSessionId(), streamHosts, xmppSession.getConfiguration().getDefaultResponseTimeout()); // 5.3.3 Target Acknowledges Bytestream // 6.3.3 Target Acknowledges Bytestream xmppSession.send(iq.createResult(Socks5ByteStream.streamHostUsed(s5bSession.getSessionId(), s5bSession.getStreamHost()))); return s5bSession; } catch (IOException e) { // If the Target tries but is unable to connect to any of the StreamHosts and it does not wish to attempt a connection from its side, it MUST return an <item-not-found/> error to the Requester. xmppSession.send(iq.createError(Condition.ITEM_NOT_FOUND)); throw new CompletionException(e); } }); return new AsyncResult<>(completableFuture); }
}).thenAccept(rosterDelimiter -> setGroupDelimiter(rosterDelimiter != null ? rosterDelimiter.getRosterDelimiter() : null)); } else { rosterDelimiterQuery = new AsyncResult<>(CompletableFuture.completedFuture(null));