package com.kixeye.kixmpp.server.module.muc; /* * #%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.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import io.netty.util.concurrent.Promise; import org.fusesource.hawtdispatch.Task; import org.jdom2.Element; import org.jdom2.Namespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.kixeye.kixmpp.KixmppJid; import com.kixeye.kixmpp.handler.KixmppStanzaHandler; import com.kixeye.kixmpp.server.KixmppServer; import com.kixeye.kixmpp.server.cluster.message.RoomBroadcastTask; import com.kixeye.kixmpp.server.cluster.message.RoomTask; import com.kixeye.kixmpp.server.module.KixmppServerModule; import com.kixeye.kixmpp.server.module.bind.BindKixmppServerModule; /** * Handles presence. * * @author ebahtijaragic */ public class MucKixmppServerModule implements KixmppServerModule { private static final Logger logger = LoggerFactory.getLogger(MucKixmppServerModule.class); private Set<MucRoomMessageListener> messageListeners = Collections.newSetFromMap(new ConcurrentHashMap<MucRoomMessageListener, Boolean>()); private KixmppServer server; private ConcurrentHashMap<String, MucService> services = new ConcurrentHashMap<>(); private MucHistoryProvider historyProvider = new MucHistoryProvider() { private final List<MucHistory> emptyList = Collections.unmodifiableList(new ArrayList<MucHistory>(0)); @Override public Promise<List<MucHistory>> getHistory(KixmppJid roomJid, KixmppJid userJid, Integer maxChars, Integer maxStanzas, Integer seconds, String since) { Promise<List<MucHistory>> promise = server.createPromise(); promise.setSuccess(emptyList); return promise; } }; /** * @see com.kixeye.kixmpp.server.module.KixmppModule#install(com.kixeye.kixmpp.server.KixmppServer) */ public void install(KixmppServer server) { this.server = server; this.server.getEventEngine().registerGlobalStanzaHandler("presence", JOIN_ROOM_HANDLER); this.server.getEventEngine().registerGlobalStanzaHandler("presence", LEAVE_ROOM_HANDLER); this.server.getEventEngine().registerGlobalStanzaHandler("message", ROOM_MESSAGE_HANDLER); } /** * @see com.kixeye.kixmpp.server.module.KixmppModule#uninstall(com.kixeye.kixmpp.server.KixmppServer) */ public void uninstall(KixmppServer server) { this.server.getEventEngine().unregisterGlobalStanzaHandler("presence", JOIN_ROOM_HANDLER); this.server.getEventEngine().unregisterGlobalStanzaHandler("presence", LEAVE_ROOM_HANDLER); this.server.getEventEngine().unregisterGlobalStanzaHandler("message", ROOM_MESSAGE_HANDLER); } /** * @param listener the listener to add */ public void addRoomMessageListener(MucRoomMessageListener listener) { messageListeners.add(listener); } /** * @param listener the listener to remove */ public void removeRoomMessageListener(MucRoomMessageListener listener) { messageListeners.remove(listener); } /** * Publish a message for the listeners to pick up. * * @param roomJid * @param sender * @param messages */ protected void publishMessage(KixmppJid roomJid, KixmppJid sender, String senderNickname, String... messages) { server.getEventEngine().publishTask( roomJid, new InvokeListenersTask(this, roomJid, sender, senderNickname, messages) ); } /** * Adds a {@link InMemoryMucService} * * @param name * @return */ public MucService addService(String name) { return addService(name.toLowerCase(), new InMemoryMucService(server, name)); } /** * Adds a {@link MucService}. * * @param name * @param service * @return */ public MucService addService(String name, MucService service) { MucService prevService = services.putIfAbsent(name.toLowerCase(), service); return prevService == null ? service : prevService; } /** * Gets a {@link MucService}. * * @param name * @return */ public MucService getService(String name) { return services.get(name); } /** * @see com.kixeye.kixmpp.server.module.KixmppModule#getFeatures(io.netty.channel.Channel) */ public List<Element> getFeatures(Channel channel) { return null; } /** * @return the historyProvider */ public MucHistoryProvider getHistoryProvider() { return historyProvider; } /** * @param historyProvider the historyProvider to set */ public void setHistoryProvider(MucHistoryProvider historyProvider) { this.historyProvider = historyProvider; } /** * Figures out what to do with a {@link RoomTask}. * * @param roomTask */ public void handleClusterTask(RoomTask roomTask) { if (roomTask instanceof RoomBroadcastTask) { RoomBroadcastTask broadcastTask = (RoomBroadcastTask)roomTask; KixmppJid roomJid = new KixmppJid(broadcastTask.getRoomId(), broadcastTask.getServiceSubDomain() + "." + server.getDomain()); publishMessage(roomJid, broadcastTask.getFromRoomJid(), broadcastTask.getNickname(), broadcastTask.getMessages()); } MucService service = getService(roomTask.getServiceSubDomain()); if (service == null) { return; } MucRoom room = service.getRoom(roomTask.getRoomId()); if (room == null) { return; } roomTask.setRoom(room); server.getEventEngine().publishTask(room.getRoomJid(),roomTask); } private KixmppStanzaHandler JOIN_ROOM_HANDLER = new KixmppStanzaHandler() { /** * @see com.kixeye.kixmpp.server.KixmppStanzaHandler#handle(io.netty.channel.Channel, org.jdom2.Element) */ public void handle(Channel channel, Element stanza) { Element x = stanza.getChild("x", Namespace.getNamespace("http://jabber.org/protocol/muc")); if (x != null) { KixmppJid fullRoomJid = KixmppJid.fromRawJid(stanza.getAttributeValue("to")); MucService service = services.get(fullRoomJid.getDomain().toLowerCase().replace("." + server.getDomain(), "")); if (service != null) { MucRoom room = service.getRoom(fullRoomJid.getNode()); if (room != null) { server.getEventEngine().publishTask(room.getRoomJid(), new JoinRoomTask(channel, room, fullRoomJid.getResource(), x)); } // TODO handle else } // TODO handle else } } }; private KixmppStanzaHandler LEAVE_ROOM_HANDLER = new KixmppStanzaHandler() { /** * @see com.kixeye.kixmpp.server.KixmppStanzaHandler#handle(io.netty.channel.Channel, org.jdom2.Element) */ public void handle(Channel channel, Element stanza) { if (stanza.getAttribute("type") != null && stanza.getAttribute("to") != null) { if (stanza.getAttributeValue("type").equals("unavailable")) { KixmppJid fullRoomJid = KixmppJid.fromRawJid(stanza.getAttributeValue("to")); MucService service = services.get(fullRoomJid.getDomain().toLowerCase().replace("." + server.getDomain(), "")); if (service != null) { MucRoom room = service.getRoom(fullRoomJid.getNode()); if (room != null) { server.getEventEngine().publishTask(room.getRoomJid(), new LeaveRoomTask(channel, room, fullRoomJid.getResource())); } } } } } }; private KixmppStanzaHandler ROOM_MESSAGE_HANDLER = new KixmppStanzaHandler() { /** * @see com.kixeye.kixmpp.server.KixmppStanzaHandler#handle(io.netty.channel.Channel, org.jdom2.Element) */ public void handle(Channel channel, Element stanza) { if ("groupchat".equals(stanza.getAttributeValue("type"))) { KixmppJid fullRoomJid = KixmppJid.fromRawJid(stanza.getAttributeValue("to")); MucService service = services.get(fullRoomJid.getDomain().toLowerCase().replace("." + server.getDomain(), "")); if (service != null) { MucRoom room = service.getRoom(fullRoomJid.getNode()); if (room != null) { Element body = stanza.getChild("body", stanza.getNamespace()); server.getEventEngine().publishTask(room.getRoomJid(), new ReceiveMessageTask(channel.attr(BindKixmppServerModule.JID).get(), room, body.getText())); } // TODO handle else } // TODO handle else } } }; private static class JoinRoomTask extends Task { private final Channel channel; private final MucRoom room; private final String nickname; private final Element x; public JoinRoomTask(Channel channel, MucRoom room, String nickname, Element x) { this.channel = channel; this.room = room; this.nickname = nickname; this.x = x; } public void run() { room.join(channel, nickname, x); } } private static class LeaveRoomTask extends Task { private final Channel channel; private final MucRoom room; private final String nickname; public LeaveRoomTask(Channel channel, MucRoom room, String nickname) { this.channel = channel; this.room = room; this.nickname = nickname; } public void run() { room.userLeft(channel, nickname); } } private static class ReceiveMessageTask extends Task { private final KixmppJid sender; private final MucRoom room; private final String body; public ReceiveMessageTask(KixmppJid sender, MucRoom room, String body) { this.sender = sender; this.room = room; this.body = body; } public void run() { room.receiveMessages(sender, true, body); } } private static class InvokeListenersTask extends Task { private final MucKixmppServerModule module; private final KixmppJid roomJid; private final KixmppJid sender; private final String senderNickname; private final String[] messages; /** * @param module * @param roomJid * @param sender * @param senderNickname * @param messages */ public InvokeListenersTask(MucKixmppServerModule module, KixmppJid roomJid, KixmppJid sender, String senderNickname, String[] messages) { this.module = module; this.roomJid = roomJid; this.sender = sender; this.senderNickname = senderNickname; this.messages = messages; } public void run() { for (MucRoomMessageListener listener : module.messageListeners) { try { listener.handle(roomJid, sender, senderNickname, messages); } catch (Exception e) { logger.error("Error while invoking listener: [{}].", listener, e); } } } } }