/*
* 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.xmppclient;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import tigase.net.IOService;
import tigase.net.SocketThread;
import tigase.server.Command;
import tigase.server.ConnectionManager;
import tigase.server.Iq;
import tigase.server.Packet;
import tigase.server.ReceiverTimeoutHandler;
import tigase.util.DNSResolver;
import tigase.util.RoutingsContainer;
import tigase.util.TigaseStringprepException;
import tigase.xml.Element;
import tigase.xmpp.Authorization;
import tigase.xmpp.BareJID;
import tigase.xmpp.JID;
import tigase.xmpp.PacketErrorTypeException;
import tigase.xmpp.StanzaType;
import tigase.xmpp.XMPPIOService;
import tigase.xmpp.XMPPProcessorIfc;
import tigase.xmpp.XMPPResourceConnection;
/**
* Class ClientConnectionManager Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class ClientConnectionManager extends ConnectionManager<XMPPIOService<Object>> {
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger(ClientConnectionManager.class
.getName());
private static final String XMLNS = "jabber:client";
private static final String ROUTINGS_PROP_KEY = "routings";
private static final String ROUTING_MODE_PROP_KEY = "multi-mode";
private static final boolean ROUTING_MODE_PROP_VAL = true;
private static final String ROUTING_ENTRY_PROP_KEY = ".+";
private static final String WHITE_CHAR_ACK_PROP_KEY = "white-char-ack";
private static final boolean WHITE_CHAR_ACK_PROP_VAL = false;
private static final String XMPP_ACK_PROP_KEY = "xmpp-ack";
private static final boolean XMPP_ACK_PROP_VAL = false;
private static final String SOCKET_CLOSE_WAIT_PROP_KEY = "socket-close-wait";
private static final long SOCKET_CLOSE_WAIT_PROP_DEF = 1;
protected SeeOtherHostIfc see_other_host_strategy = null;
protected RoutingsContainer routings = null;
private final Map<String, XMPPProcessorIfc> processors =
new ConcurrentHashMap<String, XMPPProcessorIfc>();
private final ReceiverTimeoutHandler stoppedHandler = newStoppedHandler();
private final ReceiverTimeoutHandler startedHandler = newStartedHandler();
private boolean white_char_ack = WHITE_CHAR_ACK_PROP_VAL;
private boolean xmpp_ack = XMPP_ACK_PROP_VAL;
private long socket_close_wait_time = SOCKET_CLOSE_WAIT_PROP_DEF;
/**
* This is mostly for testing purpose. We want to investigate massive (10k per
* node) connections drops at the same time during tests with Tsung. I suspect
* this might be due to problems with one of the tsung VMs working in the
* cluster generating load. If I am right then all disconnects should come
* from only one or just a few machines. If I am not right disconnects should
* be distributed evenly among all Tsung IPs.
*/
private IPMonitor ipMonitor = new IPMonitor();
/**
* Method description
*
* @param params
* @return
*/
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
Map<String, Object> props = super.getDefaults(params);
Boolean r_mode =
(Boolean) params.get(getName() + "/" + ROUTINGS_PROP_KEY + "/"
+ ROUTING_MODE_PROP_KEY);
String see_other_host_class =
(String) params.get(SeeOtherHostIfc.CM_SEE_OTHER_HOST_CLASS_PROPERTY);
see_other_host_strategy = getSeeOtherHostInstance(see_other_host_class);
props.put( SeeOtherHostIfc.CM_SEE_OTHER_HOST_CLASS_PROP_KEY, see_other_host_class );
if ( see_other_host_strategy != null ){
see_other_host_strategy.getDefaults( props, params );
}
if (r_mode == null) {
props.put(ROUTINGS_PROP_KEY + "/" + ROUTING_MODE_PROP_KEY, ROUTING_MODE_PROP_VAL);
// If the server is configured as connection manager only node then
// route packets to SM on remote host where is default routing
// for external component.
// Otherwise default routing is to SM on localhost
if (params.get("config-type").equals(GEN_CONFIG_CS)
&& (params.get(GEN_EXT_COMP) != null)) {
String[] comp_params = ((String) params.get(GEN_EXT_COMP)).split(",");
props.put(ROUTINGS_PROP_KEY + "/" + ROUTING_ENTRY_PROP_KEY, DEF_SM_NAME + "@"
+ comp_params[1]);
} else {
props.put(ROUTINGS_PROP_KEY + "/" + ROUTING_ENTRY_PROP_KEY, DEF_SM_NAME + "@"
+ DNSResolver.getDefaultHostname());
}
}
String acks = (String) params.get(XMPP_STANZA_ACK);
if (acks != null) {
String[] acks_arr = acks.split(",");
for (String ack_type : acks_arr) {
if (STANZA_WHITE_CHAR_ACK.equals(ack_type)) {
white_char_ack = true;
}
if (STANZA_XMPP_ACK.equals(ack_type)) {
xmpp_ack = true;
}
}
}
props.put(WHITE_CHAR_ACK_PROP_KEY, white_char_ack);
props.put(XMPP_ACK_PROP_KEY, xmpp_ack);
props.put(SOCKET_CLOSE_WAIT_PROP_KEY, SOCKET_CLOSE_WAIT_PROP_DEF);
return props;
}
/**
* Method description
*
* @return
*/
@Override
public String getDiscoCategoryType() {
return "c2s";
}
/**
* Method description
*
* @return
*/
@Override
public String getDiscoDescription() {
return "Client connection manager";
}
public SeeOtherHostIfc getSeeOtherHostInstance(String see_other_host_class) {
if ( log.isLoggable( Level.FINEST ) ){
log.finest( "Configuring see_other_host strategy for: " + see_other_host_class );
}
if (see_other_host_class == null)
see_other_host_class = SeeOtherHostIfc.CM_SEE_OTHER_HOST_CLASS_PROP_DEF_VAL;
if (see_other_host_class.equals("none"))
return null;
try {
see_other_host_strategy =
(SeeOtherHostIfc) Class.forName(see_other_host_class).newInstance();
} catch (Exception e) {
log.log(Level.SEVERE, "Can not instantiate see_other_host strategy for class: "
+ see_other_host_class, e);
}
return see_other_host_strategy;
}
/**
* This method can be overwritten in extending classes to get a different
* packets distribution to different threads. For PubSub, probably better
* packets distribution to different threads would be based on the sender
* address rather then destination address.
*
* @param packet
* @return
*/
@Override
public int hashCodeForPacket(Packet packet) {
if (packet.getPacketFrom() != null
&& getComponentId().getBareJID().equals(packet.getPacketFrom().getBareJID())) {
return packet.getPacketFrom().hashCode();
} else {
return packet.getTo().hashCode();
}
}
/**
* Method description
*
* @param packet
*/
@Override
public void processPacket(final Packet packet) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing packet: {0}", packet.toStringSecure());
}
if (packet.isCommand() && (packet.getCommand() != Command.OTHER)) {
processCommand(packet);
} else {
if (!writePacketToSocket(packet)) {
// Connection closed or broken, send message back to the SM
// if this is not IQ result...
// Ignore also all presence packets with available, unavailble
if ((packet.getType() != StanzaType.result)
&& (packet.getType() != StanzaType.available)
&& (packet.getType() != StanzaType.unavailable)
&& (packet.getType() != StanzaType.error)
&& !((packet.getElemName() == "presence") && (packet.getType() == null))) {
try {
Packet error =
Authorization.ITEM_NOT_FOUND.getResponseMessage(packet,
"The user connection is no longer active.", true);
addOutPacket(error);
} catch (PacketErrorTypeException e) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Ups, already error packet. Dropping it to prevent infinite loop.");
}
}
}
// In case the SessionManager lost synchronization for any
// reason, let's
// notify it that the user connection no longer exists.
// But in case of mass-disconnects we might have lot's of
// presences
// floating around, so just skip sending stream_close for all
// the
// offline presences
if ((packet.getType() != StanzaType.unavailable)
&& (packet.getPacketFrom() != null)) {
if (packet.getStanzaTo() != null) {
Packet command =
Command.STREAM_CLOSED_UPDATE.getPacket(packet.getStanzaTo(),
packet.getPacketFrom(), StanzaType.set, UUID.randomUUID().toString());
command.setPacketFrom(packet.getPacketTo());
command.setPacketTo(packet.getPacketFrom());
// Note! we don't want to receive response to this request,
// thus STREAM_CLOSED_UPDATE instead of STREAM_CLOSED
addOutPacket(command);
// addOutPacketWithTimeout(command, stoppedHandler, 15l,
// TimeUnit.SECONDS);
if (log.isLoggable(Level.FINE)) {
log.log(
Level.FINE,
"Sending a command to close the remote session for non-existen {0} connection: {1}",
new Object[] { getName(), command.toStringSecure() });
}
} else {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.FINE,
"Stream close update without an user JID, skipping for packet: {0}",
new Object[] { packet });
}
}
}
}
} // end of else
}
/**
* Method description
*
* @param serv
* @return
*/
@Override
public Queue<Packet> processSocketData(XMPPIOService<Object> serv) {
// String id = getUniqueId(serv);
JID id = serv.getConnectionId();
// String hostname =
// (String)serv.getSessionData().get(serv.HOSTNAME_KEY);
Packet p = null;
while ((p = serv.getReceivedPackets().poll()) != null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing socket data: {0} from connection: {1}",
new Object[] { p.toStringSecure(), id });
}
// Sometimes xmlns is not set for the packet. Usually it does not
// cause any problems but when the packet is sent over the s2s, ext
// or cluster connection it may be quite problematic.
// Let's force jabber:client xmlns for all packets received from c2s
// connection
// Ups, some packets like starttls or sasl-auth have own XMLNS,
// overwriting it here is not really a good idea. We have to check first
// if the xmlns is not set and then force it to jabber:client
if (p.getAttribute("xmlns") == null) {
p.setXMLNS(XMLNS);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "XMLNS set for packet: {0} from connection: {1}",
new Object[] { p.toStringSecure(), id });
}
}
// p.setPacketFrom(getFromAddress(id));
p.setPacketFrom(id);
JID receiver = serv.getDataReceiver();
if (receiver != null) {
p.setPacketTo(serv.getDataReceiver());
addOutPacket(p);
} else {
// Hm, receiver is not set yet..., ignoring
if (log.isLoggable(Level.INFO)) {
log.log(
Level.INFO,
"Hm, receiver is not set yet (misconfiguration error)..., ignoring: {0}, connection: {1}",
new Object[] { p.toStringSecure(), serv });
}
}
// TODO: Implement sending 'req' attributes by the server too
} // end of while ()
return null;
}
/**
* Method description
*
* @param port_props
*/
@Override
public void reconnectionFailed(Map<String, Object> port_props) {
}
/**
* Method description
*
* @param service
* @return
*/
@Override
public boolean serviceStopped(XMPPIOService<Object> service) {
boolean result = super.serviceStopped(service);
xmppStreamClosed(service);
return result;
}
@Override
public void serviceStarted(XMPPIOService<Object> service) {
super.serviceStarted(service);
String id = getUniqueId(service);
JID connectionId = getFromAddress(id);
service.setConnectionId(connectionId);
}
/**
* Method description
*
* @param props
*/
@Override
public void setProperties(Map<String, Object> props) {
super.setProperties(props);
if (props.get(WHITE_CHAR_ACK_PROP_KEY) != null) {
white_char_ack = (Boolean) props.get(WHITE_CHAR_ACK_PROP_KEY);
}
if (props.get(XMPP_ACK_PROP_KEY) != null) {
xmpp_ack = (Boolean) props.get(XMPP_ACK_PROP_KEY);
}
if (props.get(SOCKET_CLOSE_WAIT_PROP_KEY) != null) {
socket_close_wait_time = (Long) props.get(SOCKET_CLOSE_WAIT_PROP_KEY);
}
if (props.size() == 1) {
// If props.size() == 1, it means this is a single property update
// and this component does not support single property change for the rest
// of it's settings
return;
}
String see_other_host_class =
(String) props.get(SeeOtherHostIfc.CM_SEE_OTHER_HOST_CLASS_PROP_KEY);
see_other_host_strategy = getSeeOtherHostInstance(see_other_host_class);
if ( see_other_host_strategy != null ){
see_other_host_strategy.setProperties( props );
}
boolean routing_mode =
(Boolean) props.get(ROUTINGS_PROP_KEY + "/" + ROUTING_MODE_PROP_KEY);
routings = new RoutingsContainer(routing_mode);
int idx = (ROUTINGS_PROP_KEY + "/").length();
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(ROUTINGS_PROP_KEY + "/")
&& !entry.getKey().equals(ROUTINGS_PROP_KEY + "/" + ROUTING_MODE_PROP_KEY)) {
routings.addRouting(entry.getKey().substring(idx), (String) entry.getValue());
} // end of if (entry.getKey().startsWith(ROUTINGS_PROP_KEY + "/"))
} // end of for ()
}
/**
* Method description
*/
@Override
public void start() {
super.start();
ipMonitor = new IPMonitor();
ipMonitor.start();
}
/**
* Method description
*/
@Override
public void stop() {
super.stop();
ipMonitor.stopThread();
}
/**
* Method description
*
* @param service
*/
@Override
public void tlsHandshakeCompleted(XMPPIOService<Object> service) {
}
/**
* Method description
*
* @param serv
*/
@Override
public void xmppStreamClosed(XMPPIOService<Object> serv) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Stream closed: {0}", serv.getConnectionId());
}
// It might be a Bosh service in which case it is ignored here.
// The method may be called more than one time for a single
// connection but we want to send a notification just once
if ((serv.getXMLNS() == XMLNS)
&& (serv.getSessionData().get("stream-closed") == null)) {
serv.getSessionData().put("stream-closed", "stream-closed");
ipMonitor.addDisconnect(serv.getRemoteAddress());
if (serv.getDataReceiver() != null) {
Packet command =
Command.STREAM_CLOSED.getPacket(serv.getConnectionId(),
serv.getDataReceiver(), StanzaType.set, UUID.randomUUID().toString());
// In case of mass-disconnects, adjust the timeout properly
addOutPacketWithTimeout(command, stoppedHandler, 120l, TimeUnit.SECONDS);
log.log(Level.FINE, "Service stopped, sending packet: {0}", command);
// // For testing only.
// System.out.println("Service stopped: " +
// service.getUniqueId());
// Thread.dumpStack();
// // For testing only.
// System.out.println("Service stopped: " +
// service.getUniqueId());
// Thread.dumpStack();
} else {
log.fine("Service stopped, before stream:stream received");
}
serv.stop();
}
}
/**
* Method description
*
* @param serv
* @param attribs
* @return
*/
@Override
public String xmppStreamOpened(XMPPIOService<Object> serv, Map<String, String> attribs) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Stream opened: {0}", attribs);
}
String lang = attribs.get("xml:lang");
final String hostname = attribs.get("to");
final String from = attribs.get("from");
BareJID fromJID = null;
if (from != null) {
try {
fromJID = BareJID.bareJIDInstance(from);
} catch (TigaseStringprepException ex) {
log.log(Level.CONFIG, "From JID violates RFC6122 (XMPP:Address Format): ", ex);
return "<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'"
+ " id='tigase-error-tigase'" + " from='" + getDefVHostItem() + "'"
+ " version='1.0' xml:lang='en'>" + "<stream:error>"
+ "<improper-addressing xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+ "</stream:error>" + "</stream:stream>";
} // end of: try-catch
} // end of: if (from != null) {
if (lang == null) {
lang = "en";
}
if (hostname == null) {
return "<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'"
+ " id='tigase-error-tigase'" + " from='" + getDefVHostItem() + "'"
+ " version='1.0' xml:lang='en'>" + "<stream:error>"
+ "<improper-addressing xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+ "</stream:error>" + "</stream:stream>";
} // end of if (hostname == null)
if (!isLocalDomain(hostname)) {
return "<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'"
+ " id='tigase-error-tigase'" + " from='" + getDefVHostItem() + "'"
+ " version='1.0' xml:lang='en'>" + "<stream:error>"
+ "<host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+ "</stream:error>" + "</stream:stream>";
} // end of if (!hostnames.contains(hostname))
if (fromJID != null && see_other_host_strategy != null) {
BareJID see_other_host =
see_other_host_strategy.findHostForJID(fromJID, getDefHostName());
if (see_other_host != null && !see_other_host.equals(getDefHostName())) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Sending redirect for {0} to host {1}, connection {2}.",
new Object[] { fromJID, see_other_host, serv });
}
return "<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'"
+ " id='tigase-error-tigase'" + " from='" + getDefVHostItem() + "'"
+ " version='1.0' xml:lang='en'>" + "<stream:error>"
+ "<see-other-host xmlns='urn:ietf:params:xml:ns:xmpp-streams'>"
+ see_other_host + "</see-other-host>" + "</stream:error>"
+ "</stream:stream>";
}
} // of if (from != null )
String id = (String) serv.getSessionData().get(IOService.SESSION_ID_KEY);
if (id == null) {
id = UUID.randomUUID().toString();
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "No Session ID, generating a new one: {0}", id);
}
serv.getSessionData().put(IOService.SESSION_ID_KEY, id);
serv.setXMLNS(XMLNS);
serv.getSessionData().put(IOService.HOSTNAME_KEY, hostname);
serv.setDataReceiver(JID.jidInstanceNS(routings.computeRouting(hostname)));
String streamOpenData =
"<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'" + " from='" + hostname
+ "'" + " id='" + id + "'" + " version='1.0' xml:lang='en'>";
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Writing raw data to the socket: {0}", streamOpenData);
}
writeRawData(serv, streamOpenData);
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "DONE");
}
Packet streamOpen =
Command.STREAM_OPENED.getPacket(serv.getConnectionId(), serv.getDataReceiver(),
StanzaType.set, this.newPacketId("c2s-"), Command.DataType.submit);
Command.addFieldValue(streamOpen, "session-id", id);
Command.addFieldValue(streamOpen, "hostname", hostname);
Command.addFieldValue(streamOpen, "xml:lang", lang);
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Sending a system command to SM: {0}", streamOpen);
}
addOutPacketWithTimeout(streamOpen, startedHandler, 45l, TimeUnit.SECONDS);
log.log(Level.FINER, "DOEN 2");
} else {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Session ID is: {0}", id);
}
writeRawData(serv, "<?xml version='1.0'?><stream:stream" + " xmlns='" + XMLNS + "'"
+ " xmlns:stream='http://etherx.jabber.org/streams'" + " from='" + hostname
+ "'" + " id='" + id + "'" + " version='1.0' xml:lang='en'>");
addOutPacket(Command.GETFEATURES.getPacket(serv.getConnectionId(),
serv.getDataReceiver(), StanzaType.get, UUID.randomUUID().toString(), null));
}
return null;
}
protected JID changeDataReceiver(Packet packet, JID newAddress,
String command_sessionId, XMPPIOService<Object> serv) {
if (serv != null) {
String serv_sessionId =
(String) serv.getSessionData().get(IOService.SESSION_ID_KEY);
if (serv_sessionId.equals(command_sessionId)) {
JID old_receiver = serv.getDataReceiver();
serv.setDataReceiver(newAddress);
return old_receiver;
} else {
log.log(
Level.WARNING,
"Incorrect session ID, ignoring data redirect for: {0}, expected: {1}, received: {2}",
new Object[] { newAddress, serv_sessionId, command_sessionId });
}
}
return null;
}
@Override
protected int[] getDefPlainPorts() {
return new int[] { 5222 };
}
@Override
protected int[] getDefSSLPorts() {
return new int[] { 5223 };
}
/**
* Method <code>getMaxInactiveTime</code> returns max keep-alive time for
* inactive connection. Let's assume user should send something at least once
* every 24 hours....
*
* @return a <code>long</code> value
*/
@Override
protected long getMaxInactiveTime() {
return 24 * HOUR;
}
@Override
protected Integer getMaxQueueSize(int def) {
return def * 10;
}
@Override
protected XMPPIOService<Object> getXMPPIOServiceInstance() {
XMPPIOService<Object> result = new XMPPIOService<Object>();
result.setAckMode(white_char_ack, xmpp_ack, false);
return result;
}
protected ReceiverTimeoutHandler newStartedHandler() {
return new StartedHandler();
}
protected ReceiverTimeoutHandler newStoppedHandler() {
return new StoppedHandler();
}
protected void processCommand(Packet packet) {
XMPPIOService<Object> serv = getXMPPIOService(packet);
Iq iqc = (Iq) packet;
switch (iqc.getCommand()) {
case GETFEATURES:
if (iqc.getType() == StanzaType.result) {
List<Element> features = getFeatures(getXMPPSession(iqc));
Element elem_features = new Element("stream:features");
elem_features.addChildren(features);
elem_features.addChildren(Command.getData(iqc));
Packet result = Packet.packetInstance(elem_features, null, null);
// Is it actually needed??
// TODO: check it out and remove the line
result.setPacketTo(iqc.getTo());
writePacketToSocket(result);
} // end of if (packet.getType() == StanzaType.get)
break;
case STARTZLIB:
if (serv != null) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Starting zlib compression: {0}", serv);
}
try {
Element compressed = Command.getData(iqc, "compressed", null);
Packet p_compressed = Packet.packetInstance(compressed, null, null);
// SocketThread readThread = SocketThread.getInstance();
SocketThread.removeSocketService(serv);
// writePacketToSocket(serv, p_proceed);
serv.addPacketToSend(p_compressed);
serv.processWaitingPackets();
serv.startZLib(Deflater.BEST_COMPRESSION);
// serv.call();
SocketThread.addSocketService(serv);
} catch (IOException ex) {
log.log(Level.INFO, "Problem enabling zlib compression on the connection: ",
ex);
}
} else {
log.log(Level.WARNING, "Can't find sevice for STARTZLIB command: {0}", iqc);
}
break;
case STARTTLS:
if (serv != null) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Starting TLS for connection: {0}", serv);
}
try {
// Note:
// If you send <proceed> packet to client you must expect
// instant response from the client with TLS handshaking
// data before you will call startTLS() on server side.
// So the initial handshaking data might be lost as they
// will be processed in another thread reading data from the
// socket.
// That's why below code first removes service from reading
// threads pool and then sends <proceed> packet and starts
// TLS.
Element proceed = Command.getData(iqc, "proceed", null);
Packet p_proceed = Packet.packetInstance(proceed, null, null);
// SocketThread readThread = SocketThread.getInstance();
SocketThread.removeSocketService(serv);
// writePacketToSocket(serv, p_proceed);
serv.addPacketToSend(p_proceed);
serv.processWaitingPackets();
serv.startTLS(false);
SocketThread.addSocketService(serv);
} catch (Exception e) {
log.log(Level.WARNING, "Error starting TLS: {0}", e);
serv.forceStop();
} // end of try-catch
} else {
log.log(Level.WARNING, "Can't find sevice for STARTTLS command: {0}", iqc);
} // end of else
break;
case REDIRECT:
String command_sessionId = Command.getFieldValue(iqc, "session-id");
JID newAddress = iqc.getFrom();
JID old_receiver = changeDataReceiver(iqc, newAddress, command_sessionId, serv);
if (old_receiver != null) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Redirecting data for sessionId: {0}, to: {1}",
new Object[] { command_sessionId, newAddress });
}
Packet response = null;
response = iqc.commandResult(null);
Command.addFieldValue(response, "session-id", command_sessionId);
Command.addFieldValue(response, "action", "activate");
response.getElement().setAttribute("to", newAddress.toString());
addOutPacket(response);
} else {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Connection for REDIRECT command does not exist, ignoring " + "packet: "
+ "{0}", iqc.toStringSecure());
}
}
break;
case STREAM_CLOSED:
break;
case GETDISCO:
break;
case CLOSE:
if (serv != null) {
String streamClose = "</stream:stream>";
List<Element> err_el = packet.getElement().getChildren("/iq/command");
boolean moreToSend = false;
if (err_el != null && err_el.size() > 0) {
streamClose =
"<stream:error>" + err_el.get(0).toString() + "</stream:error>"
+ streamClose;
moreToSend = true;
}
try {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Sending stream close to the client: {0}",
streamClose);
}
serv.writeRawData(streamClose);
if (moreToSend) {
// This is kind of a workaround. serv.stop() is supposed to wait
// until all data are sent to the client, however, even then there
// is still a chance, that the connection is closed before data
// reached the client
Thread.sleep(socket_close_wait_time);
}
} catch (Exception e) {
}
serv.stop();
} else {
if (log.isLoggable(Level.FINE)) {
log.log(
Level.FINE,
"Attempt to stop non-existen service for packet: {0}, Service already stopped?",
iqc);
}
} // end of if (serv != null) else
break;
case CHECK_USER_CONNECTION:
if (serv != null) {
// It's ok, the session has been found, respond with OK.
addOutPacket(iqc.okResult((String) null, 0));
} else {
// Session is no longer active, respond with an error.
try {
addOutPacket(Authorization.ITEM_NOT_FOUND.getResponseMessage(iqc,
"Connection gone.", false));
} catch (PacketErrorTypeException e) {
// Hm, error already, ignoring...
log.log(Level.INFO, "Error packet is not really expected here: {0}",
iqc.toStringSecure());
}
}
break;
default:
writePacketToSocket(iqc);
break;
} // end of switch (pc.getCommand())
}
private List<Element> getFeatures(XMPPResourceConnection session) {
List<Element> results = new LinkedList<Element>();
for (XMPPProcessorIfc proc : processors.values()) {
Element[] features = proc.supStreamFeatures(session);
if (features != null) {
results.addAll(Arrays.asList(features));
} // end of if (features != null)
} // end of for ()
return results;
}
private JID getFromAddress(String id) {
return JID.jidInstanceNS(getName(), getDefHostName().getDomain(), id);
}
private XMPPResourceConnection getXMPPSession(Packet p) {
XMPPIOService<Object> serv = getXMPPIOService(p);
return (serv == null) ? null : (XMPPResourceConnection) serv.getSessionData().get(
"xmpp-session");
}
private class StartedHandler implements ReceiverTimeoutHandler {
/**
* Method description
*
* @param packet
* @param response
*/
@Override
public void responseReceived(Packet packet, Packet response) {
// We are now ready to ask for features....
addOutPacket(Command.GETFEATURES.getPacket(packet.getFrom(), packet.getTo(),
StanzaType.get, UUID.randomUUID().toString(), null));
}
/**
* Method description
*
* @param packet
*/
@Override
public void timeOutExpired(Packet packet) {
// If we still haven't received confirmation from the SM then
// the packet either has been lost or the server is overloaded
// In either case we disconnect the connection.
log.log(Level.INFO, "No response within time limit received for a packet: {0}",
packet.toStringSecure());
XMPPIOService<Object> serv = getXMPPIOService(packet.getFrom().toString());
if (serv != null) {
serv.stop();
} else {
log.log(
Level.FINE,
"Attempt to stop non-existen service for packet: {0}, Service already stopped?",
packet);
} // end of if (serv != null) else
}
}
private class StoppedHandler implements ReceiverTimeoutHandler {
/**
* Method description
*
* @param packet
* @param response
*/
@Override
public void responseReceived(Packet packet, Packet response) {
// Great, nothing to worry about.
if (log.isLoggable(Level.FINEST)) {
log.finest("Response for stop received...");
}
}
/**
* Method description
*
* @param packet
*/
@Override
public void timeOutExpired(Packet packet) {
// Ups, doesn't look good, the server is either oveloaded or lost
// a packet.
log.log(Level.INFO, "No response within time limit received for a packet: {0}",
packet.toStringSecure());
addOutPacketWithTimeout(packet, stoppedHandler, 60l, TimeUnit.SECONDS);
}
}
}