/*
* 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.server.xmppserver;
//~--- non-JDK imports --------------------------------------------------------
import tigase.server.Packet;
import tigase.xmpp.BareJID;
import tigase.xmpp.XMPPIOService;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- classes ----------------------------------------------------------------
/**
* Describe class ServerConnections here.
*
*
* Created: Wed Jun 11 14:26:53 2008
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class ServerConnections {
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log =
Logger.getLogger("tigase.server.xmppserver.ServerConnections");
//~--- constant enums -------------------------------------------------------
/**
* Enum description
*
*/
public enum OutgoingState {
NULL, CONNECTING, HANDSHAKING, OK;
}
//~--- fields ---------------------------------------------------------------
private CID cid = null;
private ConnectionHandlerIfc<XMPPIOService<Object>> handler = null;
/**
* Outgoing (connect) service for data packets.
*/
private XMPPIOService<Object> outgoing = null;
private OutgoingState conn_state = OutgoingState.NULL;
private long receivedPackets = 0;
private long sentPackets = 0;
///**
// * Incoming (accept) services session:id. Some servers (EJabberd) opens
// * many connections for each domain, especially when in cluster mode.
// */
//private ConcurrentHashMap<String, XMPPIOService> incoming =
// new ConcurrentHashMap<String, XMPPIOService>();
/**
* Normal packets between users on different servers
*/
private ConcurrentLinkedQueue<Packet> waitingPackets = new ConcurrentLinkedQueue<Packet>();
/**
* Controll packets for s2s connection establishing
*/
private ConcurrentLinkedQueue<Packet> waitingControlPackets =
new ConcurrentLinkedQueue<Packet>();
private ConcurrentHashMap<String, String> db_keys = new ConcurrentHashMap<String, String>();
/**
* Keeps the creation time. After some time the queue and all
* packets waiting to send should become outdated and they
* should be returned to sender and no more attempts to connect
* to the remote server should be performed.
*/
private long creationTime = System.currentTimeMillis();
//~--- constructors ---------------------------------------------------------
/**
* Creates a new <code>ServerConnections</code> instance.
*
*
* @param handler
* @param cid
*/
public ServerConnections(ConnectionHandlerIfc<XMPPIOService<Object>> handler, CID cid) {
this.handler = handler;
this.cid = cid;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param packet
*/
public void addControlPacket(Packet packet) {
waitingControlPackets.offer(packet);
}
/**
* Method description
*
*
* @param packet
*/
public void addDataPacket(Packet packet) {
if (waitingPackets.size() == 0) {
creationTime = System.currentTimeMillis();
}
waitingPackets.offer(packet);
}
/**
* Method description
*
*
* @param serv
*/
public synchronized void addOutgoing(XMPPIOService<Object> serv) {
XMPPIOService<Object> old = outgoing;
if (outgoing != serv) {
outgoing = serv;
conn_state = OutgoingState.HANDSHAKING;
}
if (old != null) {
log.info("Old outgoing connection: " + old + " replaced with new one: " + outgoing);
old.forceStop();
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public CID getCID() {
return cid;
}
/**
* Method description
*
*
* @param sessionId
*
* @return
*/
public String getDBKey(String sessionId) {
return db_keys.get(sessionId);
}
/**
* Method description
*
*
* @return
*/
public int getDBKeysSize() {
return db_keys.size();
}
/**
* Method description
*
*
* @return
*/
public OutgoingState getOutgoingState() {
return conn_state;
}
/**
* Method description
*
*
* @return
*/
public Queue<Packet> getWaitingPackets() {
return waitingPackets;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
public synchronized void handleDialbackFailure() {
if (outgoing != null) {
outgoing.forceStop();
outgoing = null;
}
conn_state = OutgoingState.NULL;
}
/**
* Method description
*
*
* @return
*/
public synchronized boolean handleDialbackSuccess() {
if ((outgoing != null) && (conn_state == OutgoingState.HANDSHAKING)) {
setValid();
ArrayDeque<Packet> all = new ArrayDeque<Packet>();
Packet packet = null;
while ((packet = waitingControlPackets.poll()) != null) {
all.offer(packet);
if (log.isLoggable(Level.FINEST)) {
log.finest("Sending on connection: " + outgoing + " control packet: " + packet);
}
}
sentPackets += waitingPackets.size();
while ((packet = waitingPackets.poll()) != null) {
all.offer(packet);
if (log.isLoggable(Level.FINEST)) {
log.finest("Sending on connection: " + outgoing + " packet: " + packet);
}
}
handler.writePacketsToSocket(outgoing, all);
return true;
} else {
log.warning("Something wrong, the method was called when the outgoing "
+ "connection is null for cid: " + cid);
outgoing = null;
conn_state = OutgoingState.NULL;
return false;
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param serv
*
* @return
*/
public boolean isOutgoing(XMPPIOService<Object> serv) {
return serv == outgoing;
}
//public void addIncoming(String session_id, XMPPIOService serv) {
// if (serv == outgoing) {
// log.info("Adding outgoing connection as incoming, packet received on "
// + "wrong connection? session_id: " + session_id);
// return;
// }
// XMPPIOService old_serv = incoming.get(session_id);
// if (old_serv != null) {
// if (old_serv == serv) {
// log.info("Adding again the same handshaking service session_id: "
// + session_id + ", unique_id: " + serv.getUniqueId());
// return;
// } else {
// log.info("Adding new handshaking service when the old one for the"
// + " same session_id exists: "
// + session_id + ", new_unique_id: " + serv.getUniqueId()
// + ", old_unique_id: " + old_serv.getUniqueId());
// old_serv.forceStop();
// }
// }
// incoming.put(session_id, serv);
//}
//public boolean sendToIncoming(String session_id, Packet packet) {
// XMPPIOService serv = incoming.get(session_id);
// if (serv != null) {
// return handler.writePacketToSocket(serv, packet);
// } else {
// return false;
// }
//}
//public void validateIncoming(String session_id, boolean valid) {
// XMPPIOService serv = incoming.get(session_id);
// if (serv != null) {
// serv.getSessionData().put("valid", valid);
// if (!valid) {
// serv.stop();
// }
// }
//}
//public boolean isIncomingValid(String session_id) {
// if (session_id == null) {
// return false;
// }
// XMPPIOService serv = incoming.get(session_id);
// if (serv == null || serv.getSessionData().get("valid") == null) {
// return false;
// } else {
// return (Boolean)serv.getSessionData().get("valid");
// }
//}
/**
* Method description
*
*
* @return
*/
public boolean isOutgoingConnected() {
return (outgoing != null) && outgoing.isConnected();
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public boolean needsConnection() {
return (conn_state == OutgoingState.NULL);
}
/**
* Method description
*
*
* @return
*/
public boolean outgoingIsNull() {
return outgoing == null;
}
/**
* Method description
*
*
* @param sessionId
* @param dbKey
*/
public void putDBKey(String sessionId, String dbKey) {
db_keys.put(sessionId, dbKey);
}
/**
* Method description
*
*
* @return
*/
public synchronized boolean sendAllControlPackets() {
if (log.isLoggable(Level.FINEST)) {
for (Packet packet : waitingControlPackets) {
log.finest("Sending on connection: " + outgoing + " control packet: " + packet);
}
}
handler.writePacketsToSocket(outgoing, waitingControlPackets);
return true;
}
/**
* Method description
*
*
* @param packet
*
* @return
*/
public synchronized boolean sendControlPacket(Packet packet) {
boolean result = false;
if ((outgoing != null) && outgoing.isConnected()
&& ((conn_state == OutgoingState.OK) || (conn_state == OutgoingState.HANDSHAKING))) {
result = handler.writePacketToSocket(outgoing, packet);
if ( !result) {
outgoing.forceStop();
outgoing = null;
} else {
if (log.isLoggable(Level.FINEST)) {
log.finest("Sent on connection: " + outgoing + " control packet: " + packet);
}
}
}
if ( !result) {
addControlPacket(packet);
if (log.isLoggable(Level.FINEST)) {
log.finest("Connection not ready: " + outgoing + " control packet added to waiting: "
+ packet);
}
}
return result;
}
/**
* Describe <code>sendPacket</code> method here.
*
* @param packet a <code>Packet</code> value
* @return a <code>boolean</code> value
*/
public synchronized boolean sendPacket(Packet packet) {
boolean result = false;
if ((outgoing != null) && outgoing.isConnected() && (conn_state == OutgoingState.OK)) {
result = handler.writePacketToSocket(outgoing, packet);
if ( !result) {
outgoing.forceStop();
outgoing = null;
} else {
++sentPackets;
if (log.isLoggable(Level.FINEST)) {
log.finest("Sent on connection: " + outgoing + " packet sent: " + packet);
}
}
}
if ( !result) {
addDataPacket(packet);
if (log.isLoggable(Level.FINEST)) {
log.finest("Connection not ready: " + outgoing + " packet added to waiting: "
+ packet);
}
}
return result;
}
/**
* Method description
*
*
* @param serv
*/
public void serviceStopped(XMPPIOService<Object> serv) {
String session_id = (String) serv.getSessionData().get(XMPPIOService.SESSION_ID_KEY);
if (session_id != null) {
db_keys.remove(session_id);
} else {
log.info("Session_ID is null for: " + serv);
}
if (serv == outgoing) {
outgoing = null;
conn_state = OutgoingState.NULL;
if (log.isLoggable(Level.FINER)) {
log.finer("Connection removed: " + outgoing + ", session id: " + session_id);
}
// return;
}
// XMPPIOService rem = incoming.remove(session_id);
// if (rem == null) {
// log.fine("No service with given SESSION_ID: " + session_id);
// } else {
// log.finer("Connection removed: " + session_id);
// }
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*/
public void setConnecting() {
conn_state = OutgoingState.CONNECTING;
}
/**
* Method description
*
*/
public void setValid() {
conn_state = OutgoingState.OK;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
public void stopAll() {
if (outgoing != null) {
outgoing.forceStop();
outgoing = null;
}
conn_state = OutgoingState.NULL;
// Set<Map.Entry<String, XMPPIOService>> set = incoming.entrySet();
// for (Map.Entry<String, XMPPIOService> entry: set) {
// entry.getValue().forceStop();
// set.remove(entry);
// }
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString() {
return "cid: " + cid + ", conn_state: " + conn_state.name() + ", outgoing: " + outgoing
+ ", waitingPackets: " + waitingPackets.size() + ", controlPacket: "
+ waitingControlPackets.size() + ", db_keys: " + db_keys.size();
}
/**
* Method description
*
*
* @return
*/
public long waitingTime() {
return (System.currentTimeMillis() - creationTime);
}
//public int incomingSize() {
// return incoming.size();
//}
}
//~ Formatted in Sun Code Convention
//~ Formatted by Jindent --- http://www.jindent.com