package tigase.xmpp.impl;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import tigase.db.NonAuthUserRepository;
import tigase.db.TigaseDBException;
import tigase.server.Packet;
import tigase.xml.Element;
import tigase.xmpp.*;
/**
* Class responsible for queueing packets (usable in connections from mobile
* clients - power usage optimalization) version 2
*
* @author andrzej
*/
public class MobileV2 extends XMPPProcessor implements XMPPProcessorIfc,
XMPPPacketFilterIfc {
private static final Logger log = Logger.getLogger(MobileV2.class.getCanonicalName());
private static final String MOBILE_EL_NAME = "mobile";
private static final String XMLNS = "http://tigase.org/protocol/mobile#v2";
private static final String ID = "mobile_v2";
private static final String[] ELEMENTS = { MOBILE_EL_NAME };
private static final String[] XMLNSS = { XMLNS };
private static final Element[] SUP_FEATURES = { new Element(MOBILE_EL_NAME,
new String[] { "xmlns" }, new String[] { XMLNS }) };
// default values
private static final int DEF_MAX_QUEUE_SIZE_VAL = 50;
// keys
private static final String MAX_QUEUE_SIZE_KEY = "max-queue-size";
private static final String QUEUE_KEY = ID + "-queue";
// ~--- fields ---------------------------------------------------------------
private int maxQueueSize = DEF_MAX_QUEUE_SIZE_VAL;
/**
* Method description
*
*
* @return
*/
@Override
public String id() {
return ID;
}
@Override
public void init(Map<String, Object> settings) throws TigaseDBException {
super.init(settings);
Integer maxQueueSizeVal = (Integer) settings.get(MAX_QUEUE_SIZE_KEY);
if (maxQueueSizeVal != null) {
maxQueueSize = maxQueueSizeVal;
}
}
/**
* Method description
*
*
* @param packet
* @param session
* @param repo
* @param results
* @param settings
*/
@Override
public void process(final Packet packet, final XMPPResourceConnection session,
final NonAuthUserRepository repo, final Queue<Packet> results,
final Map<String, Object> settings) {
if (session == null) {
return;
}
if (!session.isAuthorized()) {
try {
results.offer(session.getAuthState().getResponseMessage(packet,
"Session is not yet authorized.", false));
} catch (PacketErrorTypeException ex) {
log.log(Level.FINEST,
"ignoring packet from not authorized session which is already of type error");
}
return;
}
try {
StanzaType type = packet.getType();
switch (type) {
case set:
Element el = packet.getElement().getChild(MOBILE_EL_NAME);
String valueStr = el.getAttribute("enable");
// if value is true queuing will be enabled
boolean value =
valueStr != null && ("true".equals(valueStr) || "1".equals(valueStr));
if (session.getSessionData(QUEUE_KEY) == null) {
// session.putSessionData(QUEUE_KEY, new
// LinkedBlockingQueue<Packet>());
session.putSessionData(QUEUE_KEY, new ConcurrentHashMap<JID, Packet>());
}
session.putSessionData(XMLNS, value);
results.offer(packet.okResult((Element) null, 0));
break;
default:
results.offer(Authorization.BAD_REQUEST.getResponseMessage(packet,
"Mobile processing type is incorrect", false));
}
} catch (PacketErrorTypeException ex) {
Logger.getLogger(MobileV2.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public String[] supElements() {
return ELEMENTS;
}
@Override
public String[] supNamespaces() {
return XMLNSS;
}
@Override
public Element[] supStreamFeatures(XMPPResourceConnection session) {
if (session == null) {
return null;
}
if (!session.isAuthorized()) {
return null;
}
return SUP_FEATURES;
}
@Override
@SuppressWarnings("unchecked")
public void filter(Packet _packet, XMPPResourceConnection sessionFromSM,
NonAuthUserRepository repo, Queue<Packet> results) {
if ((sessionFromSM == null) || !sessionFromSM.isAuthorized() || (results == null)
|| (results.size() == 0)) {
return;
}
for (Iterator<Packet> it = results.iterator(); it.hasNext();) {
Packet res = it.next();
// check if packet contains destination
if (res == null || res.getPacketTo() == null) {
if (log.isLoggable(Level.FINEST)) {
log.finest("packet without destination");
}
continue;
}
// get resource connection for destination
XMPPResourceConnection session =
sessionFromSM.getParentSession().getResourceForConnectionId(res.getPacketTo());
if (session == null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "no session for destination {0} for packet {1}",
new Object[] { res.getPacketTo().toString(), res.toString() });
}
// if there is no session we should not queue
continue;
}
Map<JID, Packet> queue = (Map<JID, Packet>) session.getSessionData(QUEUE_KEY);
// if queue is not enabled we do nothing
if (!isQueueEnabled(session)) {
if (log.isLoggable(Level.FINEST)) {
log.finest("queue is no enabled");
}
if (queue != null && !queue.isEmpty()) {
if (log.isLoggable(Level.FINEST)) {
log.finest("sending packets from queue (DISABLED)");
}
for (Packet p : queue.values()) {
results.offer(p);
}
queue.clear();
}
continue;
}
// lets check if packet should be queued
if (filter(session, res, queue)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "queuing packet = {0}", res.toString());
}
it.remove();
if (queue.size() > maxQueueSize) {
if (log.isLoggable(Level.FINEST)) {
log.finest("sending packets from queue (OVERFLOW)");
}
for (Packet p : queue.values()) {
results.offer(res);
}
queue.clear();
// we are sending all items from map so there is no need
// to filter it (fix for ConcurrentModificationException)
break;
}
}
}
}
public boolean
filter(XMPPResourceConnection session, Packet res, Map<JID, Packet> queue) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "checking if packet should be queued {0}", res.toString());
}
if (res.getElemName() != "presence") {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "ignoring packet, packet is not presence: {0}",
res.toString());
}
return false;
}
StanzaType type = res.getType();
if (type != null && type != StanzaType.unavailable && type != StanzaType.available) {
return false;
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "queuing packet {0}", res.toString());
}
queue.put(res.getStanzaFrom(), res);
return true;
}
/**
* Check if queuing is enabled
*
* @param session
* @return
*/
protected boolean isQueueEnabled(XMPPResourceConnection session) {
Boolean enabled = (Boolean) session.getSessionData(XMLNS);
return enabled != null && enabled;
}
}