protected synchronized Future<?> join(@Nonnull MUCOccupant occupantData, final boolean includeInbound, final boolean includeOutbound )
    {
        if ( !(room.isFmucEnabled() && FMUC_ENABLED.getValue()) ) {
            Log.debug( "(room: '{}'): FMUC disabled, skipping FMUC join.", room.getJID() );
            return CompletableFuture.completedFuture(null);
        }

        Log.debug( "(room: '{}'): user '{}' (as '{}') attempts to join.", room.getJID(), occupantData.getUserAddress(), occupantData.getOccupantJID() );

        final CompletableFuture<?> propagateToOutbound;
        if ( !includeOutbound ) {
            Log.trace( "(room: '{}'): skip propagating to outbound, as instructed.", room.getJID() );
            propagateToOutbound = CompletableFuture.completedFuture(null);
        } else {
            // Do we need to initiate a new outbound federation (are we configured to have an outbound federation, but has one not been started yet?
            if ( outboundJoinConfiguration != null) {
                if ( outboundJoin == null ) {
                    if ( outboundJoinProgress == null ) {
                        Log.trace("(room: '{}'): FMUC configuration contains configuration for a remote MUC that needs to be joined: {}", room.getJID(), outboundJoinConfiguration.getPeer() );
                        // When a new federation is established, there's no need to explicitly propagate the join too - that's implicitly done as part of the initialization of the new federation.
                        propagateToOutbound = initiateFederationOutbound( occupantData );
                    } else {
                        Log.debug("(room: '{}'): Received a FMUC 'join' request for a remote MUC that we're already in process of joining: {}", room.getJID(), outboundJoinConfiguration.getPeer() );
                        return outboundJoinProgress.addToQueue( generateJoinStanza( occupantData ), occupantData ); // queue a new join stanza to be sent after the ongoing join completes.
                    }
                }
                else
                {
                    // TODO Doesn't this imply some kind of problem - why would we be joining a MUC that we've already joined?
                    Log.warn("(room: '{}'): FMUC configuration contains configuration for a remote MUC: {}. Federation with this MUC has already been established.", room.getJID(), outboundJoin.getPeer() );
                    // propagate to existing the existing joined FMUC node (be blocking if master/slave mode!)
                    propagateToOutbound = propagateOutbound( generateJoinStanza( occupantData ), occupantData );
                }
            } else {
                // Nothing to do!
                Log.trace( "(room: '{}'): FMUC configuration does not contain a remote MUC that needs to be joined.", room.getJID() );
                propagateToOutbound = CompletableFuture.completedFuture(null);
            }
        }

        /* TODO this implementation currently is blocking, and synchronous: inbound propagation only occurs after outbound
                propagation has been started. Is this needed? What should happen if outbound propagation fails? When the
                mode used is master/slave, then it is reasonable to assume that a failed outbound join makes the join fail
                (and thus should not be propagated to the inbound nodes. */

        // propagate to all joining FMUC nodes (need never block).
        final CompletableFuture<?> propagateToInbound;
        if ( !includeInbound ) {
            Log.trace( "(room: '{}'): skip propagating to inbound, as instructed.", room.getJID() );
            propagateToInbound = CompletableFuture.completedFuture(null);
        } else {
            propagateToInbound = propagateInbound( generateJoinStanza( occupantData ), occupantData );
        }

        // Return a Future that completes when all of the Futures constructed above complete.
        return CompletableFuture.allOf( propagateToOutbound, propagateToInbound );
    }
Makes a user in our XMPP domain join the FMUC room.

The join event is propagated to all nodes in the FMUC set that the room is part of.

When 'outbound' federation is desired, but has not yet been established (when this is the first user to join the
room), federation is initiated.

Depending on the configuration and state of the FMUC set, the join operation is considered 'blocking'. In case of
a FMUC based on master-slave mode, for example, the operation cannot continue until the remote FMUC node has
echo'd back the join. To accommodate this behavior, this method will return a Future instance. The Future will
immediately be marked as 'done' when the operation need not have 'blocking' behavior. Note that in such cases,
the Future being 'done' does explicitly <em>not</em> indicate that the remote FMUC node has received, processed
and/or accepted the event.

If the local room is not configured to federate with another room, an invocation of this method will do nothing.

@param occupantData The occupant data in which the user is joining the room.
@return A future object that completes when the stanza can be propagated locally.
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/FMUCHandler.java