/*
* 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.ConnectionManager;
import tigase.server.Packet;
import tigase.server.Permissions;
import tigase.server.xmppserver.proc.Dialback;
import tigase.server.xmppserver.proc.PacketChecker;
import tigase.server.xmppserver.proc.StartTLS;
import tigase.server.xmppserver.proc.StartZlib;
import tigase.server.xmppserver.proc.StreamError;
import tigase.server.xmppserver.proc.StreamFeatures;
import tigase.server.xmppserver.proc.StreamOpen;
import tigase.stats.StatisticsList;
import tigase.xml.Element;
import tigase.xmpp.Authorization;
import tigase.xmpp.JID;
import tigase.xmpp.PacketErrorTypeException;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Bindings;
//~--- classes ----------------------------------------------------------------
/**
* Created: Jun 14, 2010 11:59:38 AM
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class S2SConnectionManager extends ConnectionManager<S2SIOService> implements
S2SConnectionHandlerIfc<S2SIOService> {
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger
.getLogger(S2SConnectionManager.class.getName());
private static final String XMLNS_SERVER_VAL = "jabber:server";
private static final String XMLNS_CLIENT_VAL = "jabber:client";
protected static final String DB_RESULT_EL_NAME = "db:result";
protected static final String DB_VERIFY_EL_NAME = "db:verify";
/** Field description */
public static final String MAX_PACKET_WAITING_TIME_PROP_KEY = "max-packet-waiting-time";
/** Field description */
public static final String MAX_CONNECTION_INACTIVITY_TIME_PROP_KEY =
"max-inactivity-time";
/** Field description */
public static final String MAX_INCOMING_CONNECTIONS_PROP_KEY = "max-in-conns";
/** Field description */
public static final String MAX_OUT_TOTAL_CONNECTIONS_PROP_KEY = "max-out-total-conns";
/** Field description */
public static final String MAX_OUT_PER_IP_CONNECTIONS_PROP_KEY = "max-out-per-ip-conns";
/** Field description */
public static final String S2S_CONNECTION_SELECTOR_PROP_KEY = "s2s-conn-selector";
/** Field description */
public static final String S2S_CONNECTION_SELECTOR_PROP_VAL =
"tigase.server.xmppserver.S2SRandomSelector";
/** Field description */
public static final int MAX_INCOMING_CONNECTIONS_PROP_VAL = 4;
/** Field description */
public static final int MAX_OUT_TOTAL_CONNECTIONS_PROP_VAL = 1;
/** Field description */
public static final int MAX_OUT_PER_IP_CONNECTIONS_PROP_VAL = 1;
/** Field description */
public static final long MAX_PACKET_WAITING_TIME_PROP_VAL = 7 * MINUTE;
/** Field description */
public static final long MAX_CONNECTION_INACTIVITY_TIME_PROP_VAL = 15 * MINUTE;
public static final String CID_CONNECTIONS_BIND = "cidConnections";
// ~--- fields ---------------------------------------------------------------
private S2SConnectionSelector connSelector = null;
/**
* <code>maxPacketWaitingTime</code> keeps the maximum time packets can wait
* for sending in ServerPacketQueue. Packets are put in the queue only when
* connection to remote server is not established so effectively this timeout
* specifies the maximum time for connecting to remote server. If this time is
* exceeded then no more reconnecting attempts are performed and packets are
* sent back with error information.
*
* Default TCP/IP timeout is 300 seconds so we can follow this convention but
* administrator can set different timeout in server configuration.
*/
private long maxPacketWaitingTime = MAX_PACKET_WAITING_TIME_PROP_VAL;
private int maxOUTTotalConnections = MAX_OUT_TOTAL_CONNECTIONS_PROP_VAL;
private int maxOUTPerIPConnections = MAX_OUT_PER_IP_CONNECTIONS_PROP_VAL;
private long maxInactivityTime = MAX_CONNECTION_INACTIVITY_TIME_PROP_VAL;
private int maxINConnections = MAX_INCOMING_CONNECTIONS_PROP_VAL;
/**
* Outgoing and incoming connections for a given domains pair (localdomain,
* remotedomain)
*/
private Map<CID, CIDConnections> cidConnections =
new ConcurrentHashMap<CID, CIDConnections>(10000);
/**
* List of processors which should handle all traffic incoming from the
* network. In most cases if not all, these processors handle just protocol
* traffic, all the rest traffic should be passed on to MR.
*/
private Map<String, S2SProcessor> processors = new LinkedHashMap<String, S2SProcessor>(
10);
private Map<String, S2SProcessor> filters = new LinkedHashMap<String, S2SProcessor>(
10);
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param binds
*/
@Override
public void initBindings(Bindings binds) {
super.initBindings(binds);
binds.put(CID_CONNECTIONS_BIND, cidConnections);
}
/**
* Method description
*
*
* @param packet
*
* @return
*/
@Override
public boolean addOutPacket(Packet packet) {
return super.addOutPacket(packet);
}
/**
* Method description
*
*
* @param task
* @param delay
* @param unit
*/
@Override
public void addTimerTask(TimerTask task, long delay, TimeUnit unit) {
super.addTimerTask(task, delay, unit);
}
// ~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param cid
* @param createNew
*
* @return
*
* @throws LocalhostException
* @throws NotLocalhostException
*/
@Override
public CIDConnections getCIDConnections(CID cid, boolean createNew)
throws NotLocalhostException, LocalhostException {
CIDConnections result = getCIDConnections(cid);
if ((result == null) && createNew && (cid != null)) {
result = createNewCIDConnections(cid);
}
return result;
}
/**
* Method description
*
*
* @param params
*
* @return
*/
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
Map<String, Object> props = super.getDefaults(params);
props
.put(MAX_PACKET_WAITING_TIME_PROP_KEY, MAX_PACKET_WAITING_TIME_PROP_VAL / SECOND);
props.put(MAX_CONNECTION_INACTIVITY_TIME_PROP_KEY,
MAX_CONNECTION_INACTIVITY_TIME_PROP_VAL / SECOND);
props.put(MAX_INCOMING_CONNECTIONS_PROP_KEY, MAX_INCOMING_CONNECTIONS_PROP_VAL);
props.put(MAX_OUT_TOTAL_CONNECTIONS_PROP_KEY, MAX_OUT_TOTAL_CONNECTIONS_PROP_VAL);
props.put(MAX_OUT_PER_IP_CONNECTIONS_PROP_KEY, MAX_OUT_PER_IP_CONNECTIONS_PROP_VAL);
props.put(S2S_CONNECTION_SELECTOR_PROP_KEY, S2S_CONNECTION_SELECTOR_PROP_VAL);
return props;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getDiscoCategoryType() {
return "s2s";
}
/**
* Method description
*
*
* @return
*/
@Override
public String getDiscoDescription() {
return "S2S connection manager";
}
/**
*
* @param connectionCid
* @param keyCid
* @param key
* @param key_sessionId
* @param asking_sessionId
* @return
*/
@Override
public String getLocalDBKey(CID connectionCid, CID keyCid, String key,
String key_sessionId, String asking_sessionId) {
CIDConnections cid_conns = getCIDConnections(keyCid);
String result = (cid_conns == null) ? null : cid_conns.getDBKey(key_sessionId);
if (result == null) {
// In piggybacking mode the DB key can be available in the connectionCID
// rather then
// keyCID
cid_conns = getCIDConnections(connectionCid);
result = (cid_conns == null) ? null : cid_conns.getDBKey(key_sessionId);
}
return result;
}
/**
* Method description
*
*
* @param list
*/
@Override
public void getStatistics(StatisticsList list) {
super.getStatistics(list);
list.add(getName(), "CIDs number", cidConnections.size(), Level.INFO);
if (list.checkLevel(Level.FINEST)) {
long total_outgoing = 0;
long total_outgoing_tls = 0;
long total_outgoing_handshaking = 0;
long total_incoming = 0;
long total_incoming_tls = 0;
long total_dbKeys = 0;
long total_waiting = 0;
long total_waiting_control = 0;
for (Map.Entry<CID, CIDConnections> cid_conn : cidConnections.entrySet()) {
int outgoing = cid_conn.getValue().getOutgoingCount();
int outgoing_tls = cid_conn.getValue().getOutgoingTLSCount();
int outgoing_handshaking = cid_conn.getValue().getOutgoingHandshakingCount();
int incoming = cid_conn.getValue().getIncomingCount();
int incoming_tls = cid_conn.getValue().getIncomingTLSCount();
int dbKeys = cid_conn.getValue().getDBKeysCount();
int waiting = cid_conn.getValue().getWaitingCount();
int waiting_control = cid_conn.getValue().getWaitingControlCount();
if (log.isLoggable(Level.FINEST)) {
// Throwable thr = new Throwable();
//
// thr.fillInStackTrace();
// log.log(Level.FINEST, "Called from: ", thr);
log.log(Level.FINEST,
"CID: {0}, OUT: {1}, OUT_HAND: {2}, IN: {3}, dbKeys: {4}, "
+ "waiting: {5}, waiting_control: {6}",
new Object[] { cid_conn.getKey(), outgoing, outgoing_handshaking, incoming,
dbKeys, waiting, waiting_control });
}
total_outgoing += outgoing;
total_outgoing_tls += outgoing_tls;
total_outgoing_handshaking += outgoing_handshaking;
total_incoming += incoming;
total_incoming_tls += incoming_tls;
total_dbKeys += dbKeys;
total_waiting += waiting;
total_waiting_control += waiting_control;
}
list.add(getName(), "Total outgoing", total_outgoing, Level.FINEST);
list.add(getName(), "Total outgoing TLS", total_outgoing_tls, Level.FINEST);
list.add(getName(), "Total outgoing handshaking", total_outgoing_handshaking,
Level.FINEST);
list.add(getName(), "Total incoming", total_incoming, Level.FINEST);
list.add(getName(), "Total incoming TLS", total_incoming_tls, Level.FINEST);
list.add(getName(), "Total DB keys", total_dbKeys, Level.FINEST);
list.add(getName(), "Total waiting", total_waiting, Level.FINEST);
list.add(getName(), "Total control waiting", total_waiting_control, Level.FINEST);
}
}
/**
* Method description
*
*
*
* @param serv
* @return
*/
@Override
public List<Element> getStreamFeatures(S2SIOService serv) {
List<Element> results = new ArrayList<Element>(10);
for (S2SProcessor proc : processors.values()) {
proc.streamFeatures(serv, results);
}
return results;
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public boolean handlesNonLocalDomains() {
return true;
}
/**
* Method description
*
*
* @param packet
*
* @return
*/
@Override
public int hashCodeForPacket(Packet packet) {
// Calculate hash code from the destination domain name to make sure packets
// for
// a single domain are processed by the same thread to avoid race condition
// creating new connection data structures for a destination domain
if (packet.getStanzaTo() != null) {
return packet.getStanzaTo().getDomain().hashCode();
}
// Otherwise, it might be a control packet which can be processed by single
// thread
return 1;
}
/**
* Method description
*
*
* @param port_props
*/
@Override
public void initNewConnection(Map<String, Object> port_props) {
addWaitingTask(port_props);
}
/**
* Method description
*
*
* @param packet
*/
@Override
public void processPacket(Packet packet) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing packet: {0}", packet);
}
if ((packet.getStanzaTo() == null)
|| packet.getStanzaTo().getDomain().trim().isEmpty()) {
log.log(Level.WARNING, "Missing ''to'' attribute, ignoring packet...{0}"
+ "\n This most likely happens due to missconfiguration of components"
+ " domain names.", packet);
return;
}
if ((packet.getStanzaFrom() == null)
|| packet.getStanzaFrom().getDomain().trim().isEmpty()) {
log.log(Level.WARNING, "Missing ''from'' attribute, ignoring packet...{0}", packet);
return;
}
String to_hostname = packet.getStanzaTo().getDomain();
try {
// Code commented out below is not needed anymore
// following call below takes care of hostnames checking:
// getCIDConnections(cid, true);
// // Check whether addressing is correct:
//
// // We don't send packets to local domains trough s2s, there
// // must be something wrong with configuration
// if (isLocalDomainOrComponent(to_hostname)) {
//
// // Ups, remote hostname is the same as one of local hostname??
// // Internal loop possible, we don't want that....
// // Let's send the packet back....
// if (log.isLoggable(Level.INFO)) {
// log.log(Level.INFO, "Packet addresses to localhost,"
// + " I am not processing it: {0}", packet);
// }
//
// addOutPacket(Authorization.SERVICE_UNAVAILABLE.getResponseMessage(packet,
// "S2S - not delivered. Server missconfiguration.", true));
//
// return;
// }
//
String from_hostname = packet.getStanzaFrom().getDomain();
// Code commented out below is not needed anymore
// following call below takes care of hostnames checking:
// getCIDConnections(cid, true);
// // I think from_hostname needs to be different from to_hostname at
// // this point... or s2s doesn't make sense
//
// // All hostnames go through String.intern()
// if (to_hostname == from_hostname) {
// log.log(Level.WARNING,
// "Dropping incorrect packet - from_hostname == to_hostname: {0}",
// packet);
//
// return;
// }
CID cid = new CID(from_hostname, to_hostname);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Connection ID is: {0}", cid);
}
try {
CIDConnections cid_conns = getCIDConnections(cid, true);
Packet server_packet = packet.copyElementOnly();
server_packet.getElement().removeAttribute("xmlns");
cid_conns.sendPacket(server_packet);
} catch (NotLocalhostException e) {
addOutPacket(Authorization.NOT_ACCEPTABLE
.getResponseMessage(
packet,
"S2S - Incorrect source address - none of any local virtual hosts or components.",
true));
} catch (LocalhostException e) {
addOutPacket(Authorization.NOT_ACCEPTABLE
.getResponseMessage(
packet,
"S2S - Incorrect destinationaddress - one of local virtual hosts or components.",
true));
}
} catch (PacketErrorTypeException e) {
log.log(Level.WARNING, "Packet processing exception: {0}", e);
}
}
/**
* Method description
*
*
* @param serv
*
* @return
*/
@Override
public Queue<Packet> processSocketData(S2SIOService serv) {
Queue<Packet> packets = serv.getReceivedPackets();
Packet p = null;
Queue<Packet> results = new ArrayDeque<Packet>(2);
while ((p = packets.poll()) != null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing socket data: {0}", p);
}
if (p.getXMLNS() == null) {
p.setXMLNS(XMLNS_SERVER_VAL);
}
boolean processed = false;
for (S2SProcessor proc : processors.values()) {
processed |= proc.process(p, serv, results);
writePacketsToSocket(serv, results);
}
if (!processed) {
for (S2SProcessor filter : filters.values()) {
processed |= filter.process(p, serv, results);
writePacketsToSocket(serv, results);
}
}
if (!processed) {
// 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 s2s
// connection
// In theory null does not hurt, but if the packet goes through the
// cluster
// connection is gets cluster XMLNS
if (p.getXMLNS() == XMLNS_SERVER_VAL || p.getXMLNS() == null) {
p.setXMLNS(XMLNS_CLIENT_VAL);
}
try {
if (isLocalDomainOrComponent(p.getStanzaTo().getDomain())) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "{0}, Adding packet out: {1}",
new Object[] { serv, p });
}
// TODO: not entirely sure if this is a good idea....
// Let's check it out.
p.setPermissions(Permissions.REMOTE);
addOutPacket(p);
} else {
try {
serv.addPacketToSend(Authorization.NOT_ACCEPTABLE.getResponseMessage(p,
"Not a local virtual domain or component", true));
} catch (PacketErrorTypeException ex) {
}
}
} catch (Exception e) {
log.log(Level.INFO, "Unexpected exception for packet: " + p, e);
}
}
} // end of while ()
return null;
}
/**
* Method description
*
*
* @param port_props
*/
@Override
public void reconnectionFailed(Map<String, Object> port_props) {
CID cid = (CID) port_props.get("cid");
if (cid == null) {
log.log(Level.WARNING, "Protocol error cid not set for outgoing connection: {0}",
port_props);
return;
}
CIDConnections cid_conns = getCIDConnections(cid);
if (cid_conns == null) {
log.log(Level.WARNING,
"Protocol error cid_conns not found for outgoing connection: {0}", port_props);
return;
} else {
cid_conns.reconnectionFailed(port_props);
}
}
/**
* Method description
*
*
* @param elem_name
* @param connCid
* @param keyCid
* @param valid
* @param key_sessionId
* @param serv_sessionId
* @param cdata
* @param handshakingOnly
*
* @return
*/
@Override
public boolean sendVerifyResult(String elem_name, CID connCid, CID keyCid,
Boolean valid, String key_sessionId, String serv_sessionId, String cdata,
boolean handshakingOnly) {
CIDConnections cid_conns = getCIDConnections(connCid);
if (cid_conns != null) {
Packet verify_valid =
getValidResponse(elem_name, keyCid, key_sessionId, valid, cdata);
if (handshakingOnly) {
cid_conns.sendHandshakingOnly(verify_valid);
return true;
} else {
return cid_conns.sendControlPacket(serv_sessionId, verify_valid);
}
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Can't find CID connections for cid: {0}, can't send verify response.",
keyCid);
}
}
return false;
}
/**
* Method description
*
*
* @param serv
*/
@Override
public void serviceStarted(S2SIOService serv) {
super.serviceStarted(serv);
log.log(Level.FINEST, "s2s connection opened: {0}", serv);
for (S2SProcessor proc : processors.values()) {
proc.serviceStarted(serv);
}
}
/**
* Method description
*
*
* @param serv
*
* @return
*/
@Override
public boolean serviceStopped(S2SIOService serv) {
boolean result = super.serviceStopped(serv);
if (result) {
for (S2SProcessor proc : processors.values()) {
proc.serviceStopped(serv);
}
}
return result;
}
// ~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param props
*/
@Override
public void setProperties(Map<String, Object> props) {
super.setProperties(props);
// TODO: Make used processors list a configurable thing
processors.clear();
processors.put( Dialback.class.getName(), new Dialback() );
processors.put( StartTLS.class.getName(), new StartTLS() );
processors.put( StartZlib.class.getName(), new StartZlib() );
processors.put( StreamError.class.getName(), new StreamError() );
processors.put( StreamFeatures.class.getName(), new StreamFeatures() );
processors.put( StreamOpen.class.getName(), new StreamOpen() );
for ( S2SProcessor proc : processors.values() ) {
proc.init( this );
}
filters.clear();
filters.put( PacketChecker.class.getName(), new PacketChecker() );
for ( S2SProcessor filter : filters.values() ) {
filter.init( this );
}
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;
}
maxPacketWaitingTime = (Long) props.get(MAX_PACKET_WAITING_TIME_PROP_KEY) * SECOND;
maxInactivityTime =
(Long) props.get(MAX_CONNECTION_INACTIVITY_TIME_PROP_KEY) * SECOND;
maxOUTTotalConnections = (Integer) props.get(MAX_OUT_TOTAL_CONNECTIONS_PROP_KEY);
maxOUTPerIPConnections = (Integer) props.get(MAX_OUT_PER_IP_CONNECTIONS_PROP_KEY);
maxINConnections = (Integer) props.get(MAX_INCOMING_CONNECTIONS_PROP_KEY);
String selector_str = (String) props.get(S2S_CONNECTION_SELECTOR_PROP_KEY);
try {
connSelector = (S2SConnectionSelector) Class.forName(selector_str).newInstance();
} catch (Exception e) {
log.log(Level.SEVERE, "Incorrect s2s connection selector class provided: {0}",
selector_str);
log.log(Level.SEVERE, "Selector initialization exception: ", e);
}
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param serv
*/
@Override
public void tlsHandshakeCompleted(S2SIOService serv) {
for (S2SProcessor proc : processors.values()) {
proc.serviceStarted(serv);
}
}
/**
* Method description
*
*
* @param ios
* @param data
*/
@Override
public void writeRawData(S2SIOService ios, String data) {
super.writeRawData(ios, data);
}
/**
* Method description
*
*
* @param serv
*/
@Override
public void xmppStreamClosed(S2SIOService serv) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "{0}, Stream closed.", new Object[] { serv });
}
for (S2SProcessor proc : processors.values()) {
proc.streamClosed(serv);
}
}
/**
* Method description
*
*
* @param serv
* @param attribs
*
* @return
*/
@Override
public String xmppStreamOpened(S2SIOService serv, Map<String, String> attribs) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "{0}, Stream opened: {1}", new Object[] { serv, attribs });
}
StringBuilder sb = new StringBuilder(256);
for (S2SProcessor proc : processors.values()) {
String res = proc.streamOpened(serv, attribs);
if (res != null) {
sb.append(res);
}
}
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "{0}, Sending stream open: {1}", new Object[] { serv, sb });
}
return (sb.length() == 0) ? null : sb.toString();
}
// ~--- get methods ----------------------------------------------------------
@Override
protected int[] getDefPlainPorts() {
return new int[] { 5269 };
}
@Override
protected long getMaxInactiveTime() {
return maxInactivityTime;
}
@Override
protected S2SIOService getXMPPIOServiceInstance() {
return new S2SIOService();
}
@Override
protected boolean isHighThroughput() {
return true;
}
// ~--- methods --------------------------------------------------------------
private CIDConnections createNewCIDConnections(CID cid) throws NotLocalhostException,
LocalhostException {
if (!isLocalDomainOrComponent(cid.getLocalHost())) {
throw new NotLocalhostException("This is not a valid localhost: "
+ cid.getLocalHost());
}
if (isLocalDomainOrComponent(cid.getRemoteHost())) {
throw new LocalhostException("This is not a valid remotehost: "
+ cid.getRemoteHost());
}
CIDConnections cid_conns =
new CIDConnections(cid, this, connSelector, maxINConnections,
maxOUTTotalConnections, maxOUTPerIPConnections, maxPacketWaitingTime);
cidConnections.put(cid, cid_conns);
return cid_conns;
}
// ~--- get methods ----------------------------------------------------------
private CIDConnections getCIDConnections(CID cid) {
if (cid == null) {
return null;
}
return cidConnections.get(cid);
}
private Packet getValidResponse(String elem_name, CID cid, String id, Boolean valid,
String cdata) {
Element elem = new Element(elem_name);
if (cdata != null) {
elem.setCData(cdata);
}
if (valid != null) {
if (valid.booleanValue()) {
elem.addAttribute("type", "valid");
} else {
elem.addAttribute("type", "invalid");
}
}
if (id != null) {
elem.addAttribute("id", id);
}
Packet result =
Packet.packetInstance(elem, JID.jidInstanceNS(cid.getLocalHost()),
JID.jidInstanceNS(cid.getRemoteHost()));
return result;
}
}
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com