package tigase.xmpp.impl; import java.util.Iterator; import java.util.Map; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; 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 1 * * @author andrzej */ public class MobileV1 extends XMPPProcessor implements XMPPProcessorIfc, XMPPPacketFilterIfc { private static final Logger log = Logger.getLogger(MobileV1.class.getCanonicalName()); private static final String MOBILE_EL_NAME = "mobile"; private static final String XMLNS = "http://tigase.org/protocol/mobile#v1"; private static final String ID = "mobile_v1"; 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; private static final long DEF_MAX_TIMEOUT_VAL = 6 * 60 * 1000; // keys private static final String LAST_TRANSFER_KEY = ID + "-last-transfer"; private static final String MAX_QUEUE_SIZE_KEY = "max-queue-size"; private static final String MAX_TIMEOUT_KEY = "max-timeout"; private static final String QUEUE_KEY = ID + "-queue"; private static final String TIMEOUT_KEY = ID + "-timeout"; // ~--- fields --------------------------------------------------------------- private int maxQueueSize = DEF_MAX_QUEUE_SIZE_VAL; private long maxTimeout = DEF_MAX_TIMEOUT_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; } Long maxTimeoutVal = (Long) settings.get(MAX_TIMEOUT_KEY); if (maxTimeoutVal != null) { maxTimeout = maxTimeoutVal; } } /** * 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 (el.getAttribute("timeout") != null) { // we got timeout so we should set it for this session long timeout = Long.parseLong(el.getAttribute("timeout")); setTimeout(session, timeout); } if (session.getSessionData(QUEUE_KEY) == null) { session.putSessionData(QUEUE_KEY, new LinkedBlockingQueue<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(MobileV1.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; } Queue<Packet> queue = (Queue<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)"); } Packet p; while ((p = queue.poll()) != null) { results.offer(p); } } 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)"); } Packet p; while ((p = queue.poll()) != null) { results.offer(p); } queue.clear(); } } } } public boolean filter(XMPPResourceConnection session, Packet res, Queue<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.offer(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; } /** * Check timeout for queue * * @param session * @return */ protected boolean isTimedOut(XMPPResourceConnection session) { Long lastAccessTime = (Long) session.getSessionData(LAST_TRANSFER_KEY); if (lastAccessTime == null) { return true; } if (lastAccessTime + getTimeout(session) < System.currentTimeMillis()) { return true; } return false; } /** * Update last send time * * @param session */ protected void updateLastAccessTime(XMPPResourceConnection session) { session.putSessionData(LAST_TRANSFER_KEY, System.currentTimeMillis()); } /** * Get timeout used for session queue * * @param session * @return */ private long getTimeout(XMPPResourceConnection session) { Long timeout = (Long) session.getSessionData(TIMEOUT_KEY); if (timeout == null) { return maxTimeout; } return timeout; } /** * Set timeout for session queue * * @param session * @param timeout */ private void setTimeout(XMPPResourceConnection session, long timeout) { if (timeout == 0) { session.removeSessionData(TIMEOUT_KEY); } else { session.putSessionData(TIMEOUT_KEY, timeout); } } }