package com.kixeye.kixmpp.handler; /* * #%L * KIXMPP * %% * Copyright (C) 2014 KIXEYE, Inc * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import io.netty.channel.Channel; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.fusesource.hawtdispatch.Dispatch; import org.fusesource.hawtdispatch.DispatchQueue; import org.fusesource.hawtdispatch.Task; import org.jdom2.Element; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.kixeye.kixmpp.KixmppJid; import com.kixeye.kixmpp.KixmppStreamEnd; import com.kixeye.kixmpp.KixmppStreamStart; import com.kixeye.kixmpp.tuple.Tuple; /** * An event engine that uses a {@link DispatchQueue}. * * @author ebahtijaragic */ public class KixmppEventEngine { private static final String HANDLER_WILDCARD = "*"; private final ConcurrentHashMap<Tuple, Set<KixmppStanzaHandler>> stanzaHandlers = new ConcurrentHashMap<>(); private final Set<KixmppConnectionHandler> connectionHandlers = Collections.newSetFromMap(new ConcurrentHashMap<KixmppConnectionHandler, Boolean>()); private final Set<KixmppStreamHandler> streamHandlers = Collections.newSetFromMap(new ConcurrentHashMap<KixmppStreamHandler, Boolean>()); private final Set<KixmppSessionHandler> sessionHandlers = Collections.newSetFromMap(new ConcurrentHashMap<KixmppSessionHandler, Boolean>()); private final LoadingCache<String, DispatchQueue> queues = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) .build(new CacheLoader<String, DispatchQueue>() { public DispatchQueue load(String key) throws Exception { return Dispatch.createQueue(key); } }); /** * Publishes a stanza. * * @param channel * @param stanza */ public void publishStanza(Channel channel, Element stanza) { String to = stanza.getAttributeValue("to"); DispatchQueue queue; try { if (to != null) { queue = queues.get("address:" + to); } else { queue = queues.get("channel:" + channel.hashCode()); } } catch (ExecutionException e) { throw new RuntimeException(e); } if (to != null) { Set<KixmppStanzaHandler> recipientHandlers = stanzaHandlers.get(Tuple.from(stanza.getQualifiedName(), KixmppJid.fromRawJid(to))); if (recipientHandlers != null) { for (KixmppStanzaHandler handler : recipientHandlers) { queue.execute(new ExecuteStanzaHandler(handler, channel, stanza)); } } recipientHandlers = stanzaHandlers.get(Tuple.from(HANDLER_WILDCARD, KixmppJid.fromRawJid(to))); if (recipientHandlers != null) { for (KixmppStanzaHandler handler : recipientHandlers) { queue.execute(new ExecuteStanzaHandler(handler, channel, stanza)); } } } Set<KixmppStanzaHandler> globalHandlers = stanzaHandlers.get(Tuple.from(stanza.getQualifiedName())); if (globalHandlers != null) { for (KixmppStanzaHandler handler : globalHandlers) { queue.execute(new ExecuteStanzaHandler(handler, channel, stanza)); } } globalHandlers = stanzaHandlers.get(Tuple.from(HANDLER_WILDCARD)); if (globalHandlers != null) { for (KixmppStanzaHandler handler : globalHandlers) { queue.execute(new ExecuteStanzaHandler(handler, channel, stanza)); } } } /** * Published an arbitrary task for serial execution. * * @param jid * @param task */ public void publishTask(KixmppJid jid, Task task) { DispatchQueue queue; try { if (jid != null) { queue = queues.get("address:" + jid.getFullJid()); } else { queue = queues.get("default"); } } catch (ExecutionException e) { throw new RuntimeException(e); } queue.execute(task); } /** * Published an arbitrary task for serial execution. * * @param channel * @param task */ public void publishTask(Channel channel, Task task) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } queue.execute(task); } /** * Publishes that a channel has connected. * * @param channel */ public void publishConnected(Channel channel) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } for (KixmppConnectionHandler handler : connectionHandlers) { queue.execute(new ExecuteConnectionConnectedHandler(handler, channel)); } } /** * Publishes that a channel has disconnected. * * @param channel */ public void publishDisconnected(Channel channel) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } for (KixmppConnectionHandler handler : connectionHandlers) { queue.execute(new ExecuteConnectionDisconnectedHandler(handler, channel)); } } /** * Publishes a stream start event. * * @param channel * @param streamStart */ public void publishStreamStart(Channel channel, KixmppStreamStart streamStart) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } for (KixmppStreamHandler handler : streamHandlers) { queue.execute(new ExecuteStreamStartHandler(handler, channel, streamStart)); } } /** * Publishes a stream end event. * * @param channel * @param streamEnd */ public void publishStreamEnd(Channel channel, KixmppStreamEnd streamEnd) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } for (KixmppStreamHandler handler : streamHandlers) { queue.execute(new ExecuteStreamEndHandler(handler, channel, streamEnd)); } } /** * Publishes a session start event. * * @param channel */ public void publishSessionStart(Channel channel) { DispatchQueue queue; try { queue = queues.get("channel:" + channel.hashCode()); } catch (ExecutionException e) { throw new RuntimeException(e); } for (KixmppSessionHandler handler : sessionHandlers) { queue.execute(new ExecuteSessionStartHandler(handler, channel)); } } /** * Registers a handler to listen to connection events. * * @param handler */ public void registerConnectionHandler(KixmppConnectionHandler handler) { connectionHandlers.add(handler); } /** * Unregisters a connection handler. * * @param handler */ public void unregisterConnectionHandler(KixmppConnectionHandler handler) { connectionHandlers.remove(handler); } /** * Registers a stream handler. * * @param handler */ public void registerStreamHandler(KixmppStreamHandler handler) { streamHandlers.add(handler); } /** * Unregisters a stream handler. * * @param handler */ public void unregisterStreamHandler(KixmppStreamHandler handler) { streamHandlers.remove(handler); } /** * Registers a session handler. * * @param handler */ public void registerSessionHandler(KixmppSessionHandler handler) { sessionHandlers.add(handler); } /** * Unregisters a session handler. * * @param handler */ public void unregisterSessionHandler(KixmppSessionHandler handler) { sessionHandlers.remove(handler); } /** * Registers a stanza handler. * * @param qualifiedName * @param jid * @param handler */ public void registerStanzaHandler(KixmppJid jid, String qualifiedName, KixmppStanzaHandler handler) { Tuple key = Tuple.from(qualifiedName, jid); Set<KixmppStanzaHandler> handlers = stanzaHandlers.get(key); if (handlers == null) { Set<KixmppStanzaHandler> newHandlers = Collections.newSetFromMap(new ConcurrentHashMap<KixmppStanzaHandler, Boolean>()); handlers = stanzaHandlers.putIfAbsent(key, newHandlers); if (handlers == null) { handlers = newHandlers; } } handlers.add(handler); } /** * Registers a stanza handler. * * @param jid * @param handler */ public void registerStanzaHandler(KixmppJid jid, KixmppStanzaHandler handler) { registerStanzaHandler(jid, HANDLER_WILDCARD, handler); } /** * Registers a stanza handler. * * @param handler */ public void registerGlobalStanzaHandler(String qualifiedName, KixmppStanzaHandler handler) { Tuple key = Tuple.from(qualifiedName); Set<KixmppStanzaHandler> handlers = stanzaHandlers.get(key); if (handlers == null) { Set<KixmppStanzaHandler> newHandlers = Collections.newSetFromMap(new ConcurrentHashMap<KixmppStanzaHandler, Boolean>()); handlers = stanzaHandlers.putIfAbsent(key, newHandlers); if (handlers == null) { handlers = newHandlers; } } handlers.add(handler); } /** * Registers a stanza handler. * * @param handler */ public void registerGlobalStanzaHandler(KixmppStanzaHandler handler) { registerGlobalStanzaHandler(HANDLER_WILDCARD, handler); } /** * Unregisters a stanza handler. * * @param jid * @param qualifiedName * @param handler */ public void unregisterStanzaHandler(KixmppJid jid, String qualifiedName, KixmppStanzaHandler handler) { Set<KixmppStanzaHandler> handlers = stanzaHandlers.get(Tuple.from(qualifiedName, jid)); if (handlers != null) { handlers.remove(handler); } } /** * Unregisters a stanza handler. * * @param jid * @param handler */ public void unregisterStanzaHandler(KixmppJid jid, KixmppStanzaHandler handler) { unregisterStanzaHandler(jid, HANDLER_WILDCARD, handler); } /** * Unregisters a stanza handler. * * @param qualifiedName * @param handler */ public void unregisterGlobalStanzaHandler(String qualifiedName, KixmppStanzaHandler handler) { Set<KixmppStanzaHandler> handlers = stanzaHandlers.get(Tuple.from(qualifiedName)); if (handlers != null) { handlers.remove(handler); } } /** * Unregisters a stanza handler. * * @param handler */ public void unregisterGlobalStanzaHandler(KixmppStanzaHandler handler) { unregisterGlobalStanzaHandler(HANDLER_WILDCARD, handler); } /** * Unregisters all the handlers. */ public void unregisterAll() { stanzaHandlers.clear(); connectionHandlers.clear(); streamHandlers.clear(); sessionHandlers.clear(); } private static class ExecuteStanzaHandler extends Task { private final KixmppStanzaHandler handler; private final Channel channel; private final Element stanza; public ExecuteStanzaHandler(KixmppStanzaHandler handler, Channel channel, Element stanza) { this.handler = handler; this.channel = channel; this.stanza = stanza; } public void run() { handler.handle(channel, stanza); } } private static class ExecuteConnectionConnectedHandler extends Task { private final KixmppConnectionHandler handler; private final Channel channel; public ExecuteConnectionConnectedHandler(KixmppConnectionHandler handler, Channel channel) { this.handler = handler; this.channel = channel; } public void run() { handler.handleConnected(channel); } } private static class ExecuteConnectionDisconnectedHandler extends Task { private final KixmppConnectionHandler handler; private final Channel channel; public ExecuteConnectionDisconnectedHandler(KixmppConnectionHandler handler, Channel channel) { this.handler = handler; this.channel = channel; } public void run() { handler.handleDisconnected(channel); } } private static class ExecuteStreamStartHandler extends Task { private final KixmppStreamHandler handler; private final Channel channel; private final KixmppStreamStart start; public ExecuteStreamStartHandler(KixmppStreamHandler handler, Channel channel, KixmppStreamStart start) { this.handler = handler; this.channel = channel; this.start = start; } public void run() { handler.handleStreamStart(channel, start); } } private static class ExecuteStreamEndHandler extends Task { private final KixmppStreamHandler handler; private final Channel channel; private final KixmppStreamEnd end; public ExecuteStreamEndHandler(KixmppStreamHandler handler, Channel channel, KixmppStreamEnd end) { this.handler = handler; this.channel = channel; this.end = end; } public void run() { handler.handleStreamEnd(channel, end); } } private static class ExecuteSessionStartHandler extends Task { private final KixmppSessionHandler handler; private final Channel channel; public ExecuteSessionStartHandler(KixmppSessionHandler handler, Channel channel) { this.handler = handler; this.channel = channel; } public void run() { handler.handleSessionStart(channel); } } }