private static Optional<SoftwareVersion> getSoftwareVersion(XmppClient client) { try { return Optional.ofNullable(client.getManager(SoftwareVersionManager.class).getSoftwareVersion(client.getDomain()).getResult()); } catch (Exception e) { return Optional.empty(); } }
/** * 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)); }
private AsyncResult<List<Item>> discoverServices(Predicate<InfoNode> predicate) { // First discover the items of the server. // Then, for each item, discover the features of the item, but ignore any exceptions. return discoverItems(xmppSession.getDomain()).thenCompose(itemDiscovery -> { Collection<CompletionStage<List<Item>>> stages = itemDiscovery.getItems().stream() .map(item -> discoverInformation(item.getJid()).thenApply(infoDiscovery -> { if (predicate.test(infoDiscovery)) { return Collections.singletonList(item); } return Collections.<Item>emptyList(); }).handle((items, throwable) -> { // If one disco#info fails, don't let the whole discoverServices() method fail. // Instead of failing, return an empty list, other services can hopefully be discovered successfully. if (throwable != null) { return Collections.<Item>emptyList(); } else { return items; } })) .collect(Collectors.toList()); return CompletionStages.allOf(stages); }); }
/** * 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)); } }
/** * 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)); }
/** * Publishes an avatar to your VCard. * * @param imageData The avatar image data, which must be in PNG format. {@code null} resets the avatar. * @return The async result. * @see <a href="https://xmpp.org/extensions/xep-0153.html#publish">3.1 User Publishes Avatar</a> */ public final AsyncResult<Void> publishAvatar(byte[] imageData) { String hash = imageData != null ? XmppUtils.hash(imageData) : null; AvatarMetadata.Info info = imageData != null ? new AvatarMetadata.Info(imageData.length, hash, hash) : null; // Try publishing to vCard first. If this fails, don't immediately throw an exception, but try PEP first. return publishToVCard(imageData, null, hash) .whenComplete((result, e) -> { if (e != null) { logger.warning("Failed to publish avatar to vCard."); } }) .thenCompose((aVoid) -> publishToPersonalEventingService(imageData, hash, info)) .thenRun(() -> { }); }
@Override public Result run() { final ServiceDiscoveryManager serviceDiscoveryManager = client.getManager(ServiceDiscoveryManager.class); final MultiUserChatManager multiUserChatManager = client.getManager(MultiUserChatManager.class); try { List<ChatService> chatServices = multiUserChatManager.discoverChatServices().getResult(); if (chatServices.size() < 1) { return Result.FAILED; } final ChatService chatService = chatServices.get(0); final ChatRoom room = chatService.createRoom(UUID.randomUUID().toString()); room.enter("test"); try { final DataForm.Field mam = room.getConfigurationForm().get().findField("mam"); if (mam != null) { return Result.PASSED; } } catch (ExecutionException | InterruptedException e) { //ignore } final Set<String> features = serviceDiscoveryManager.discoverInformation(room.getAddress()).getResult().getFeatures(); final boolean mam = TestUtils.hasAnyone(MAM.NAMESPACES,features); room.destroy().getResult(); return mam ? Result.PASSED : Result.FAILED; } catch (XmppException e) { return Result.FAILED; } }
/** * Discovers the rooms, where a contact is in. * * @param contact The contact, which must be a full JID. * @return The async result with the items, {@link rocks.xmpp.extensions.disco.model.items.Item#getJid()} has the room address, and {@link rocks.xmpp.extensions.disco.model.items.Item#getName()}} has the nickname. * @see <a href="https://xmpp.org/extensions/xep-0045.html#disco-client">6.7 Discovering Client Support for MUC</a> */ public AsyncResult<List<Item>> discoverEnteredRooms(Jid contact) { return serviceDiscoveryManager.discoverItems(contact, ROOMS_NODE).thenApply(ItemNode::getItems); }
PrivateDataManager privateDataManager = xmppSession.getManager(PrivateDataManager.class); AsyncResult<RosterDelimiter> query = privateDataManager.getData(RosterDelimiter.class); rosterDelimiterQuery = query.exceptionally(e -> { }).thenAccept(rosterDelimiter -> setGroupDelimiter(rosterDelimiter != null ? rosterDelimiter.getRosterDelimiter() : null)); } else { rosterDelimiterQuery = new AsyncResult<>(CompletableFuture.completedFuture(null)); return rosterDelimiterQuery.thenCompose(result -> { return xmppSession.query(IQ.get(rosterRequest)).thenApply(iq -> { Roster rosterResult = iq.getExtension(Roster.class); Roster currentRoster;
return getAvailableStreamHosts().thenCompose(streamHosts -> { final Jid requester = xmppSession.getConnectedResource(); return xmppSession.query(IQ.set(target, new Socks5ByteStream(sessionId, streamHosts, hash))).whenComplete((a, e) -> ).thenComposeAsync(result -> { return xmppSession.query(IQ.set(usedStreamHost.getJid(), Socks5ByteStream.activate(sessionId, target))).thenApply(aVoid -> { try { return new S5bSession(sessionId, socket, usedStreamHost.getJid(), xmppSession.getConfiguration().getDefaultResponseTimeout());
byte[] imageData = loadFromCache(hash); if (imageData != null) { return new AsyncResult<>(CompletableFuture.completedFuture(imageData)); vCard = vCardManager.getVCard(contact); return vCard.handle((result, exc) -> { return new AsyncResult<>(CompletableFuture.completedFuture(new byte[0]));
/** * Discovers the SOCKS5 proxies. * * @return The async result with the proxies. * @see <a href="https://xmpp.org/extensions/xep-0065.html#disco">4. Discovering Proxies</a> */ public AsyncResult<List<StreamHost>> discoverProxies() { // First discover the items which identify the proxy host. return serviceDiscoveryManager.discoverServices(Identity.proxyByteStreams()).thenCompose(items -> { // For each proxy service, send a disco request to it (to discover stream hosts) Collection<CompletionStage<List<StreamHost>>> stages = items.stream() .map(service -> xmppSession.query(IQ.get(service.getJid(), new Socks5ByteStream())).thenApply(result -> { Socks5ByteStream socks5ByteStream = result.getExtension(Socks5ByteStream.class); if (socks5ByteStream != null) { return socks5ByteStream.getStreamHosts(); } return Collections.<StreamHost>emptyList(); })) .collect(Collectors.toList()); // Combine all discovery of all stream hosts into one future. return CompletionStages.allOf(stages); }); }
/** * 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 vCardManager.getVCard().thenApply(result -> { }).thenAccept(vCard -> { if (avatar != null) {
return this.entityCapabilitiesManager.isSupported(StreamInitiation.NAMESPACE, recipient).thenCompose(isSISupported -> { if (!isSISupported) { throw new CompletionException(new XmppException("Feature not supported")); return this.entityCapabilitiesManager.isSupported(SIFileTransferOffer.NAMESPACE, recipient).thenCompose(isSIFileTransferSupported -> { if (!isSIFileTransferSupported) { throw new CompletionException(new XmppException("Feature not supported")); return this.streamInitiationManager.initiateStream(recipient, fileTransfer, mType, timeout, sessionId).handle((byteStreamSession, e) -> { if (e != null) { if (e instanceof CompletionException) {
/** * Stores the roster group delimiter in the private storage and afterwards sets it. * * @param groupDelimiter The group delimiter. * @return The async result. * @see #setGroupDelimiter(String) * @see <a href="https://xmpp.org/extensions/xep-0083.html">XEP-0083: Nested Roster Groups</a> */ public final AsyncResult<Void> storeGroupDelimiter(String groupDelimiter) { return privateDataManager.storeData(RosterDelimiter.of(groupDelimiter)) .thenAccept(result -> setGroupDelimiter(groupDelimiter)); }
/** * Becomes invisible. * * @return The async result. */ public AsyncResult<Void> becomeInvisible() { return xmppSession.query(IQ.set(InvisibleCommand.INVISIBLE)).thenRun(() -> { synchronized (InvisibilityManager.this) { invisible = true; } }); }
/** * Adds a bookmark. * * @param bookmark The bookmark. * @return The async result. */ public final AsyncResult<Void> addBookmark(Bookmark bookmark) { return privateDataManager.getData(BookmarkStorage.class).thenCompose(bookmarkStorage -> { Collection<Bookmark> bookmarks = new ArrayDeque<>(bookmarkStorage.getBookmarks()); bookmarks.remove(bookmark); bookmarks.add(bookmark); return privateDataManager.storeData(new BookmarkStorage(bookmarks)); }); }
private static Consumer<ByteStreamEvent> createSessionListener(final String sessionId, final CompletableFuture<ByteStreamSession> completableFuture) { return e -> { if (sessionId.equals(e.getSessionId())) { // Auto-accept the inbound stream e.accept().whenComplete((byteStreamSession, throwable) -> { if (throwable != null) { completableFuture.completeExceptionally(throwable); } else { completableFuture.complete(byteStreamSession); } }); } }; }
/** * Checks whether the entity supports the given feature. If the features are already known and cached * * @param feature The feature. * @param jid The JID, which should usually be a full JID. * @return The async result with true, if this entity supports the feature. */ public final AsyncResult<Boolean> isSupported(String feature, Jid jid) { return discoverCapabilities(jid) .handle((infoNode, e) -> { if (e == null) { return infoNode.getFeatures().contains(feature); } else { if (e.getCause() instanceof StanzaErrorException) { return false; } throw (CompletionException) e; } }); }