private CompletableFuture<Void> broadcastPresence( Presence presence, boolean isJoinPresence, @Nonnull MUCOccupant sender) {
        if (presence == null) {
            return CompletableFuture.completedFuture(null);
        }

        if (!presence.getFrom().asBareJID().equals(this.getJID())) {
            // At this point, the 'from' address of the to-be broadcast stanza can be expected to be the role-address
            // of the subject, or more broadly: it's bare JID representation should match that of the room. If that's not
            // the case then there's a bug in Openfire. Catch this here, as otherwise, privacy-sensitive data is leaked.
            // See: OF-2152
            throw new IllegalArgumentException("Broadcast presence stanza's 'from' JID " + presence.getFrom() + " does not match room JID: " + this.getJID());
        }

        // Create a defensive copy, to prevent modifications to leak back to the invoker.
        final Presence stanza = presence.createCopy();

        // Some clients send a presence update to the room, rather than to their own nickname.
        if ( JiveGlobals.getBooleanProperty("xmpp.muc.presence.overwrite-to-room", true) && stanza.getTo() != null && stanza.getTo().getResource() == null && sender.getOccupantJID() != null) {
            stanza.setTo( sender.getOccupantJID() );
        }

        if (!canBroadcastPresence(sender.getRole())) {
            // Just send the presence to the sender of the presence
            final Presence selfPresence = createSelfPresenceCopy(stanza, isJoinPresence);
            sender.send(selfPresence);
            return CompletableFuture.completedFuture(null);
        }

        // If FMUC is active, propagate the presence through FMUC first. Note that when a master-slave mode is active,
        // we need to wait for an echo back, before the message can be broadcast locally. The 'propagate' method will
        // return a CompletableFuture object that is completed as soon as processing can continue.
        return fmucHandler.propagate(stanza, sender)
            // DO NOT use 'thenRunAsync', as that will cause issues with clustering (it uses an executor that overrides the contextClassLoader, causing ClassNotFound exceptions in ClusterExternalizableUtil).
            .thenRun(() -> broadcast(stanza, isJoinPresence));
    }
Broadcasts the specified presence to all room occupants. If the presence belongs to a
user whose role cannot be broadcast then the presence will only be sent to the presence's
user. On the other hand, the JID of the user that sent the presence won't be included if the
room is semi-anon and the target occupant is not a moderator.

@param presence the presence to broadcast.
@param isJoinPresence If the presence is sent in the context of joining the room.
@param sender The occupant that initiated the presence broadcast
xmppserver/src/main/java/org/jivesoftware/openfire/muc/MUCRoom.java