/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.cluster.strategy; import tigase.server.Packet; import tigase.server.xmppsession.SessionManagerHandler; import tigase.stats.StatisticsList; import tigase.xml.Element; import tigase.xmpp.BareJID; import tigase.xmpp.JID; import tigase.xmpp.StanzaType; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; /** * Created: May 13, 2009 9:53:44 AM * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public class SMNonCachingAllNodes implements ClusteringStrategyIfc { /** * Variable <code>log</code> is a class logger. */ private static final Logger log = Logger .getLogger(SMNonCachingAllNodes.class.getName()); private CopyOnWriteArrayList<JID> cl_nodes_list = new CopyOnWriteArrayList<JID>(); private SessionManagerHandler sm = null; // Simple random generator, we do not need a strong randomization here. // Just enough to ensure better traffic distribution private Random rand = new Random(); public void setSessionManagerHandler(SessionManagerHandler sm) { this.sm = sm; } /** * Method description * * * @param jid * * @return */ @Override public boolean containsJid(BareJID jid) { return false; } /** * Method description * * * @return */ @Override public List<JID> getAllNodes() { return cl_nodes_list; } /** * Method description * * * @param jid * * @return */ @Override public JID[] getConnectionIdsForJid(BareJID jid) { return null; } /** * Method description * * * @param params * * @return */ @Override public Map<String, Object> getDefaults(Map<String, Object> params) { return null; } /** * Method description * * * @param jid * * @return */ @Override public List<JID> getNodesForJid(JID jid) { return getAllNodes(); } /** * Method description * * * @param list */ @Override public void getStatistics(StatisticsList list) { list.add("cl-caching-strat", "Connected nodes", cl_nodes_list.size(), Level.INFO); } /** * Method description * * * @return */ @Override public boolean hasCompleteJidsInfo() { return false; } /** * Method description * * * @return */ @Override public boolean needsSync() { return false; } /** * Method description * * * @param jid */ @Override public void nodeConnected(JID jid) { boolean result = cl_nodes_list.addIfAbsent(jid); log.log(Level.FINE, "Cluster nodes: {0}, added: {1}", new Object[] { cl_nodes_list, result }); } /** * Method description * * * @param jid */ @Override public void nodeDisconnected(JID jid) { boolean result = cl_nodes_list.remove(jid); log.log(Level.FINE, "Cluster nodes: {0}, removed: {1}", new Object[] { cl_nodes_list, result }); } /** * Method description * * * @param props */ @Override public void setProperties(Map<String, Object> props) { } /** * Method description * * * @param sm * @param results * @param jid */ @Override public void userDisconnected(Queue<Packet> results, ConnectionRecord rec) { } /** * Method description * * * @param sm * @param results * @param jid */ @Override public void usersConnected(Queue<Packet> results, ConnectionRecord... rec) { } /* * (non-Javadoc) * * @see * tigase.cluster.strategy.ClusteringStrategyIfc#getNodesForPacket(tigase. * xml.Element) */ @Override public List<JID> getNodesForPacketForward(JID fromNode, Set<JID> visitedNodes, Packet packet) { // If the packet visited other nodes already it means it went through other // checking // like isSuitableForForward, etc... so there is no need for doing it again if (visitedNodes != null) { List<JID> result = selectNodes(fromNode, visitedNodes); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Visited nodes not null: {0}, selecting new node: {1}, for packet: {2}", new Object[] { visitedNodes, result, packet }); } return result; } // Presence status change set by the user have a special treatment: if (packet.getElemName() == "presence" && packet.getType() != StanzaType.error && packet.getStanzaFrom() != null && packet.getStanzaTo() == null) { List<JID> result = getAllNodes(); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Presence packet found: {0}, selecting all nodes: {1}", new Object[] { packet, result }); } return result; } if (isSuitableForForward(packet)) { List<JID> result = selectNodes(fromNode, visitedNodes); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Visited nodes null, selecting new node: {0}, for packet: {1}", new Object[] { result, packet }); } return result; } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet not suitable for forwarding: {0}", new Object[] { packet }); } return null; } } protected boolean isSuitableForForward(Packet packet) { // Do not forward any error packets for now. if (packet.getType() == StanzaType.error) { return false; } // Artur: Moved it to the front of the method for performance reasons. // TODO: make sure it does not affect logic. if (packet.getPacketFrom() != null && !sm.getComponentId().equals(packet.getPacketFrom())) { return false; } // This is for packet forwarding logic. // Some packets are for certain not forwarded like packets without to // attribute set. if ((packet.getStanzaTo() == null) || sm.isLocalDomain(packet.getStanzaTo().toString(), false) || sm.getComponentId().equals((packet.getStanzaTo().getBareJID()))) { return false; } // Also packets sent from the server to user are not being forwarded like // service discovery perhaps? if ((packet.getStanzaFrom() == null) || sm.isLocalDomain(packet.getStanzaFrom().toString(), false) || sm.getComponentId().equals((packet.getStanzaFrom().getBareJID()))) { return false; } // If the packet is to some external domain, it is not forwarded to other // nodes either. It is also not forwarded if it is addressed to some // component. if (!sm.isLocalDomain(packet.getStanzaTo().getDomain(), false)) { return false; } return true; } /** * @param fromNode * @param visitedNodes * @return */ private List<JID> selectNodes(JID fromNode, Set<JID> visitedNodes) { List<JID> result = null; int size = cl_nodes_list.size(); if (size == 0) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "No connected cluster nodes found, returning null"); } return null; } int idx = rand.nextInt(size); if (visitedNodes == null || visitedNodes.size() == 0) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "No visited nodes yet, trying random idx: " + idx); } try { result = Collections.singletonList(cl_nodes_list.get(idx)); } catch (IndexOutOfBoundsException ioobe) { // This may happen if the node disconnected in the meantime.... try { result = Collections.singletonList(cl_nodes_list.get(0)); } catch (IndexOutOfBoundsException ioobe2) { // Yes, this may happen too if there were only 2 nodes before // disconnect.... if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "IndexOutOfBoundsException twice! Should not happen very often, returning null"); } } } } else { for (JID jid : cl_nodes_list) { if (!visitedNodes.contains(jid)) { result = Collections.singletonList(jid); break; } } // If all nodes visited already. We have to either send it back to the // first node // or if this is the first node return null if (result == null && !sm.getComponentId().equals(fromNode)) { result = Collections.singletonList(fromNode); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "All nodes visited, sending it back to the first node: " + result); } } } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "List of result nodes: " + result); } return result; } /* * (non-Javadoc) * * @see tigase.cluster.strategy.ClusteringStrategyIfc#getNodesForUserConnect() */ @Override public List<JID> getNodesForUserConnect(JID jid) { return getAllNodes(); } /* * (non-Javadoc) * * @see * tigase.cluster.strategy.ClusteringStrategyIfc#getNodesForUserDisconnect() */ @Override public List<JID> getNodesForUserDisconnect(JID jid) { return getAllNodes(); } /* * (non-Javadoc) * * @see tigase.cluster.strategy.ClusteringStrategyIfc#getInternalCache() */ @Override @Deprecated public Object getInternalCacheData() { return null; } /* * (non-Javadoc) * * @see * tigase.cluster.strategy.ClusteringStrategyIfc#getConnectionRecords(tigase * .xmpp.BareJID) */ @Override public Set<ConnectionRecord> getConnectionRecords(BareJID bareJID) { return null; } /* * (non-Javadoc) * * @see * tigase.cluster.strategy.ClusteringStrategyIfc#getConnectionRecord(tigase * .xmpp.JID) */ @Override public ConnectionRecord getConnectionRecord(JID jid) { return null; } /* * (non-Javadoc) * * @see * tigase.cluster.strategy.ClusteringStrategyIfc#presenceUpdate(tigase.server * .Packet, tigase.cluster.strategy.ConnectionRecord) */ @Override public void presenceUpdate(Element presence, ConnectionRecord rec) { } }