use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.
the class RoutingTableImpl method addComponentRoute.
@Override
public void addComponentRoute(JID route, RoutableChannelHandler destination) {
DomainPair pair = new DomainPair("", route.getDomain());
String address = route.getDomain();
localRoutingTable.addRoute(pair, destination);
Lock lock = componentsCache.getLock(address);
lock.lock();
try {
HashSet<NodeID> nodes = componentsCache.get(address);
if (nodes == null) {
nodes = new HashSet<>();
}
nodes.add(server.getNodeID());
componentsCache.put(address, nodes);
} finally {
lock.unlock();
}
}
use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.
the class RoutingTableImpl method routeToComponent.
/**
* Routes packets that are sent to components of the XMPP domain (which are
* subdomains of the 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.
*/
private boolean routeToComponent(JID jid, Packet packet) {
if (!hasComponentRoute(jid) && !ExternalComponentManager.hasConfiguration(jid.getDomain())) {
return false;
}
// First check if the component is being hosted in this JVM
boolean routed = false;
RoutableChannelHandler route = localRoutingTable.getRoute(new JID(null, jid.getDomain(), null, true));
if (route != null) {
try {
route.process(packet);
routed = true;
} catch (UnauthorizedException e) {
Log.error("Unable to route packet " + packet.toXML(), e);
}
} else {
// Check if other cluster nodes are hosting this component
Set<NodeID> nodes = componentsCache.get(jid.getDomain());
if (nodes != null) {
for (NodeID nodeID : nodes) {
if (server.getNodeID().equals(nodeID)) {
// could have been added after our previous check)
try {
RoutableChannelHandler localRoute = localRoutingTable.getRoute(new JID(null, jid.getDomain(), null, true));
if (localRoute != null) {
localRoute.process(packet);
routed = true;
break;
}
} catch (UnauthorizedException e) {
Log.error("Unable to route packet " + packet.toXML(), e);
}
} else {
// This is a route to a local component hosted in other node
if (remotePacketRouter != null) {
routed = remotePacketRouter.routePacket(nodeID.toByteArray(), jid, packet);
if (routed) {
break;
}
}
}
}
}
}
return routed;
}
use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.
the class RoutingTableImpl method joinedCluster.
@Override
public void joinedCluster() {
// The local node joined a cluster.
//
// Upon joining a cluster, clustered caches are reset to their clustered equivalent (by the swap from the local
// cache implementation to the clustered cache implementation that's done in the implementation of
// org.jivesoftware.util.cache.CacheFactory.joinedCluster). This means that they now hold data that's
// available on all other cluster nodes. Data that's available on the local node needs to be added again.
restoreCacheContent();
Log.debug("Add the entry listeners to the corresponding caches.");
// Register a cache entry event listeners that will collect data for entries added by all other cluster nodes,
// which is intended to be used (only) in the event of a cluster split.
final ClusteredCacheEntryListener<String, ClientRoute> userCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(routeOwnersByClusterNode);
final ClusteredCacheEntryListener<DomainPair, NodeID> serversCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(s2sDomainPairsByClusterNode);
final ClusteredCacheEntryListener<String, HashSet<NodeID>> componentsCacheEntryListener = new ReverseLookupComputingCacheEntryListener<>(componentsByClusterNode, nodeIDS -> nodeIDS.stream().filter(n -> !n.equals(XMPPServer.getInstance().getNodeID())).collect(Collectors.toSet()));
// Note that, when #joinedCluster() fired, the cache will _always_ have been replaced, meaning that it won't
// have old event listeners. When #leaveCluster() fires, the cache will be destroyed. This takes away the need
// to explicitly deregister the listener in that case.
// Ensure that event listeners have been registered with the caches, before starting to simulate 'entryAdded' events,
// to prevent the possibility of having entries that are missed by the simulation because of bad timing.
usersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
anonymousUsersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
serversCache.addClusteredCacheEntryListener(serversCacheEntryListener, false, false);
componentsCache.addClusteredCacheEntryListener(componentsCacheEntryListener, true, true);
// This is not necessary for the usersSessions cache, because its content is being managed while the content
// of users cache and anonymous users cache is being managed.
Log.debug("Simulate 'entryAdded' for all data that already exists elsewhere in the cluster.");
Stream.concat(usersCache.entrySet().stream(), anonymousUsersCache.entrySet().stream()).filter(entry -> !entry.getValue().getNodeID().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> userCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue().getNodeID()));
serversCache.entrySet().stream().filter(entry -> !entry.getValue().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> serversCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue()));
componentsCache.entrySet().forEach(entry -> {
entry.getValue().forEach(nodeIdForComponent -> {
// Iterate over all node ids on which the component is known
if (!nodeIdForComponent.equals(XMPPServer.getInstance().getNodeID())) {
// Here we pretend that the component has been added by the node id on which it is reported to
// be available. This might not have been the case, but it is probably accurate. An alternative
// approach is not easily available.
componentsCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), nodeIdForComponent);
}
});
});
// Broadcast presence of local sessions to remote sessions when subscribed to presence.
// Probe presences of remote sessions when subscribed to presence of local session.
// Send pending subscription requests to local sessions from remote sessions.
// Deliver offline messages sent to local sessions that were unavailable in other nodes.
// Send available presences of local sessions to other resources of the same user.
PresenceUpdateHandler presenceUpdateHandler = XMPPServer.getInstance().getPresenceUpdateHandler();
for (LocalClientSession session : localRoutingTable.getClientRoutes()) {
// Simulate that the local session has just become available
session.setInitialized(false);
// Simulate that current session presence has just been received
presenceUpdateHandler.process(session.getPresence());
}
// TODO OF-2067: the above also (re)generates events on the local node, where these events had already occurred. Ideally, that should not happen.
// TODO OF-2066: shouldn't a similar action be done on the other nodes, so that the node that just joined gets informed about all sessions living on other cluster nodes?
}
use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.
the class MultiUserChatServiceImpl method makeOccupantsOnConnectedClusterNodeJoin.
/**
* Sends presence 'join' stanzas on behalf of chatroom occupants that are now connected to the cluster (by virtue
* of a cluster node joining the cluster) to other room occupants are local to this cluster node.
*
* Note that this method does not change state (in either the clustered cache, or local equivalents): it only sends stanzas.
*
* @param occupantsOnConnectedNodes The occupants for which to send stanzas.
* @param remoteNodeID The cluster node ID of the server that joined the cluster.
*/
private void makeOccupantsOnConnectedClusterNodeJoin(@Nullable final Set<OccupantManager.Occupant> occupantsOnConnectedNodes, @Nonnull NodeID remoteNodeID) {
Log.debug("Going to send 'join' presence stanzas on the local cluster node for {} occupant(s) of cluster node {} that is now part of our cluster.", occupantsOnConnectedNodes == null || occupantsOnConnectedNodes.isEmpty() ? 0 : occupantsOnConnectedNodes.size(), remoteNodeID);
if (occupantsOnConnectedNodes == null || occupantsOnConnectedNodes.isEmpty()) {
return;
}
if (!MUCRoom.JOIN_PRESENCE_ENABLE.getValue()) {
Log.trace("Skipping, as presences are disabled by configuration.");
return;
}
// Find all occupants that were not already on any other node in the cluster. This intends to prevent sending 'join'
// presences for occupants that are in the same room, using the same nickname, but using a client that is
// connected to a cluster node that already was in the cluster.
final Set<OccupantManager.Occupant> toAdd = occupantsOnConnectedNodes.stream().filter(occupant -> !occupantManager.exists(occupant, remoteNodeID)).collect(Collectors.toSet());
Log.trace("After accounting for occupants already in the room by virtue of having another connected device using the same nickname, {} presence stanza(s) are to be sent.", toAdd.size());
// For each, broadcast a 'join' presence in the room(s).
for (final OccupantManager.Occupant occupant : toAdd) {
Log.trace("Preparing to send 'join' stanza for occupant '{}' (using nickname '{}') of room '{}'", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
final MUCRoom chatRoom = getChatRoom(occupant.roomName);
if (chatRoom == null) {
Log.info("User {} seems to be an occupant (using nickname '{}') of a non-existent room named '{}' on newly connected cluster node(s).", occupant.realJID, occupant.nickname, occupant.roomName);
continue;
}
// At this stage, the occupant information must already be available through the clustered cache.
final MUCRole occupantRole = chatRoom.getOccupantByFullJID(occupant.getRealJID());
if (occupantRole == null) {
Log.warn("A remote cluster node ({}) tells us that user {} is supposed to be an occupant (using nickname '{}') of a room named '{}' but the data in the cluster cache does not indicate that this is true.", remoteNodeID, occupant.realJID, occupant.nickname, occupant.roomName);
continue;
}
if (!chatRoom.canBroadcastPresence(occupantRole.getRole())) {
Log.trace("Skipping join stanza, as room is configured to not broadcast presence for role {}", occupantRole.getRole());
continue;
}
// To prevent each (remaining) cluster node from broadcasting the same presence to all occupants of all cluster nodes,
// this broadcasts only to occupants on the local node.
final Set<OccupantManager.Occupant> recipients = occupantManager.occupantsForRoomByNode(occupant.roomName, XMPPServer.getInstance().getNodeID());
for (OccupantManager.Occupant recipient : recipients) {
Log.trace("Preparing stanza for recipient {} (nickname: {})", recipient.getRealJID(), recipient.getNickname());
try {
// Note that we cannot use org.jivesoftware.openfire.muc.MUCRoom.broadcastPresence() as this would attempt to
// broadcast to the user that is 'joining' to all users. We only want to broadcast locally here.
// We _need_ to go through the MUCRole for sending this stanza, as that has some additional logic (eg: FMUC).
final MUCRole recipientRole = chatRoom.getOccupantByFullJID(recipient.getRealJID());
if (recipientRole != null) {
recipientRole.send(occupantRole.getPresence());
} else {
Log.warn("Unable to find MUCRole for recipient '{}' in room {} while broadcasting 'join' presence for occupants on joining cluster node {}.", recipient.getRealJID(), chatRoom.getJID(), remoteNodeID);
// This is a partial fix that will _probably_ work if FMUC is not used. Better than nothing? (although an argument for failing-fast can be made).
XMPPServer.getInstance().getPacketRouter().route(occupantRole.getPresence());
}
} catch (Exception e) {
Log.warn("A problem occurred while notifying local occupant that user '{}' joined room '{}' as a result of a cluster node {} joining the cluster.", occupant.nickname, occupant.roomName, remoteNodeID, e);
}
}
}
Log.debug("Finished sending 'join' presence stanzas on the local cluster node.");
}
use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.
the class MultiUserChatServiceImpl method makeOccupantsOnDisconnectedClusterNodesLeave.
/**
* Sends presence 'leave' stanzas on behalf of chatroom occupants that are no longer connected to the cluster to
* room occupants that are local to this cluster node.
*
* Note that this method does not change state (in either the clustered cache, or local equivalents): it only sends stanzas.
*
* @param occupantsOnRemovedNodes The occupants for which to send stanzas
*/
private void makeOccupantsOnDisconnectedClusterNodesLeave(@Nullable final Set<OccupantManager.Occupant> occupantsOnRemovedNodes, NodeID nodeID) {
Log.debug("Going to send 'leave' presence stanzas on the local cluster node for {} occupant(s) of one or more cluster nodes that are no longer part of our cluster.", occupantsOnRemovedNodes == null || occupantsOnRemovedNodes.isEmpty() ? 0 : occupantsOnRemovedNodes.size());
if (occupantsOnRemovedNodes == null || occupantsOnRemovedNodes.isEmpty()) {
return;
}
if (!MUCRoom.JOIN_PRESENCE_ENABLE.getValue()) {
Log.trace("Skipping, as presences are disabled by configuration.");
return;
}
// Find all occupants that are now no longer on any node in the cluster. This intends to prevent sending 'leave'
// presences for occupants that are in the same room, using the same nickname, but using a client that is
// connected to a cluster node that is still in the cluster.
final Set<OccupantManager.Occupant> toRemove = occupantsOnRemovedNodes.stream().filter(occupant -> !occupantManager.exists(occupant)).collect(Collectors.toSet());
Log.trace("After accounting for occupants still in the room by virtue of having another connected device using the same nickname, {} presence stanza(s) are to be sent.", toRemove.size());
// For each, broadcast a 'leave' presence in the room(s).
for (final OccupantManager.Occupant occupant : toRemove) {
Log.trace("Preparing to send 'leave' stanza for occupant '{}' (using nickname '{}') of room '{}'", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
final MUCRoom chatRoom = getChatRoom(occupant.getRoomName());
if (chatRoom == null) {
Log.info("User {} seems to be an occupant (using nickname '{}') of a non-existent room named '{}' on disconnected cluster node(s).", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
continue;
}
// To prevent each (remaining) cluster node from broadcasting the same presence to all occupants of all remaining nodes,
// this broadcasts only to occupants on the local node.
final Set<OccupantManager.Occupant> recipients;
if (nodeID == null) {
recipients = occupantManager.occupantsForRoomByNode(occupant.getRoomName(), XMPPServer.getInstance().getNodeID());
Log.trace("Intended recipients, count: {} (occupants of the same room, on local node): {}", recipients.size(), recipients.stream().map(OccupantManager.Occupant::getRealJID).map(JID::toString).collect(Collectors.joining(", ")));
} else {
recipients = occupantManager.occupantsForRoomExceptForNode(occupant.getRoomName(), nodeID);
Log.trace("Intended recipients, count: {} (occupants of the same room, on all remaining cluster nodes): {}", recipients.size(), recipients.stream().map(OccupantManager.Occupant::getRealJID).map(JID::toString).collect(Collectors.joining(", ")));
}
for (OccupantManager.Occupant recipient : recipients) {
try {
Log.trace("Preparing stanza for recipient {} (nickname: {})", recipient.getRealJID(), recipient.getNickname());
// Note that we cannot use chatRoom.sendLeavePresenceToExistingOccupants(leaveRole) as this would attempt to
// broadcast to the user that is leaving. That user is clearly unreachable in this instance (as it lives on
// a now disconnected cluster node.
final Presence presence = new Presence(Presence.Type.unavailable);
presence.setTo(recipient.getRealJID());
presence.setFrom(new JID(chatRoom.getJID().getNode(), chatRoom.getJID().getDomain(), occupant.nickname));
final Element childElement = presence.addChildElement("x", "http://jabber.org/protocol/muc#user");
final Element item = childElement.addElement("item");
item.addAttribute("role", "none");
if (chatRoom.canAnyoneDiscoverJID() || chatRoom.getModerators().stream().anyMatch(m -> m.getUserAddress().asBareJID().equals(recipient.getRealJID().asBareJID()))) {
// Send non-anonymous - add JID.
item.addAttribute("jid", occupant.getRealJID().toString());
}
childElement.addElement("status").addAttribute("code", "333");
// We _need_ to go through the MUCRole for sending this stanza, as that has some additional logic (eg: FMUC).
final MUCRole recipientRole = chatRoom.getOccupantByFullJID(recipient.getRealJID());
Log.debug("Stanza now being sent: {}", presence.toXML());
if (recipientRole != null) {
recipientRole.send(presence);
} else {
Log.warn("Unable to find MUCRole for recipient '{}' in room {} while broadcasting 'leave' presence for occupants on disconnected cluster node(s).", recipient.getRealJID(), chatRoom.getJID());
// This is a partial fix that will _probably_ work if FMUC is not used. Better than nothing? (although an argument for failing-fast can be made).
XMPPServer.getInstance().getPacketRouter().route(presence);
}
} catch (Exception e) {
Log.warn("A problem occurred while notifying local occupant that user '{}' left room '{}' as a result of a cluster disconnect.", occupant.nickname, occupant.roomName, e);
}
}
}
Log.debug("Finished sending 'leave' presence stanzas on the local cluster node.");
}
Aggregations