private boolean routeToRemoteDomain(JID jid, Packet packet) {
        if ( !JiveGlobals.getBooleanProperty( ConnectionSettings.Server.ALLOW_ANONYMOUS_OUTBOUND_DATA, false ) )
        {
            // Disallow anonymous local users to send data to other domains than the local domain.
            if ( SessionManager.getInstance().isAnonymousClientSession(packet.getFrom()) )
            {
                Log.info( "The anonymous user '{}' attempted to send data to '{}', which is on a remote domain. Openfire is configured to not allow anonymous users to send data to remote domains.", packet.getFrom(), jid );
                return false;
            }
        }

        if (!RemoteServerManager.canAccess(jid.getDomain())) { // Check if the remote domain is in the blacklist
            Log.info( "Will not route: Remote domain {} is not accessible according to our configuration (typical causes: server federation is disabled, or domain is blacklisted).", jid.getDomain() );
            return false;
        }

        DomainPair domainPair = new DomainPair(packet.getFrom().getDomain(), jid.getDomain());

        Log.trace("Routing to remote domain: {}", packet);

        // It is possible that serversCache has an entry for this domain, while the OutgoingSessionPromise is
        // still processing its queue. Stanzas must be delivered in order, so delivery of this stanza needs to
        // be postponed until after the queue is empty (OF-2321).
        //
        // The code block below is guarded by the mutex that also guards:
        // - sending queued stanzas _after_ a connection has been established (but not the connection establishment itself),
        // - changes to the response of OutgoingServerSession#isPending() for the domainPair that is used to create the mutex
        // This will ensure that a new stanza:
        // - is supplied to OutgoingSessionPromise before queue processing is started / can start.
        // - is not supplied to OutgoingSessionPromise after queue processing has started. In that case, this code will
        //   block until the OutgoingSessionPromise is done processing, and will then route the stanza through the localRoutingTable.
        synchronized (OutgoingSessionPromise.getInstance().getMutex(domainPair))
        {
            if (OutgoingSessionPromise.getInstance().hasProcess(domainPair)) {
                Log.trace("An outgoing session for {} is in process of being established. Queuing stanza for delivery when that's done.", domainPair);
                OutgoingSessionPromise.getInstance().queue(domainPair, packet);
                return true;
            } else {
                NodeID nodeID = serversCache.get(domainPair);
                if (nodeID != null) {
                    if (server.getNodeID().equals(nodeID)) {
                        Log.trace("An outgoing session for {} is available on the local cluster node. Delivering stanza.", domainPair);
                        try {
                            localServerRoutingTable.getRoute(domainPair).process(packet);
                            return true;
                        } catch (Throwable e) {
                            Log.error("Unable to route packet {}", packet.toXML(), e);
                            return false;
                        }
                    } else {
                        if (remotePacketRouter != null) {
                            Log.trace("An outgoing session for {} is available on a remote cluster node. Asking that node to deliver stanza.", domainPair);
                            return remotePacketRouter.routePacket(nodeID.toByteArray(), jid, packet);
                        } else {
                            Log.error("An outgoing session for {} is available on a remote cluster node, but no RemotePacketRouter exists!", domainPair);
                            return false;
                        }
                    }
                } else {
                    Log.trace("A new outgoing session for {} is needed. Instantiating a new queue stanza for delivery when that's done.", domainPair);
                    OutgoingSessionPromise.getInstance().createProcess(domainPair, packet);
                    return true;
                }
            }
        }
    }
Routes packets that are sent to other XMPP domains than the local XMPP
domain.

@param jid
           the recipient of the packet to route.
@param packet
           the packet to route.
@throws PacketException
            thrown if the packet is malformed (results in the sender's
            session being shutdown).
@return {@code true} if the packet was routed successfully,
        {@code false} otherwise.
xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java