/** * $Revision: 1722 $ * $Date: 2005-07-28 15:19:16 -0700 (Thu, 28 Jul 2005) $ * * Copyright (C) 2005-2008 Jive Software. All rights reserved. * * 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. */ package org.jivesoftware.openfire.plugin.rest; import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.security.Principal; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.servlets.EventSource; import org.eclipse.jetty.servlets.EventSourceServlet; import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.jivesoftware.openfire.plugin.ofmeet.TokenManager; import org.jivesoftware.smack.*; import org.jivesoftware.smackx.muc.*; import org.jivesoftware.smackx.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.filter.*; import org.jivesoftware.openfire.plugin.rest.entity.*; public class RestEventSourceServlet extends EventSourceServlet { private static final long serialVersionUID = 1L; private static final Logger Log = LoggerFactory.getLogger(RestEventSourceServlet.class); public static final ConcurrentHashMap<String, ArrayList<RestEventSource>> eventSources = new ConcurrentHashMap<String, ArrayList<RestEventSource>>(); public static final ConcurrentHashMap<String, Map<String, MultiUserChat>> groupchats = new ConcurrentHashMap<String, Map<String, MultiUserChat>>(); public static final ConcurrentHashMap<String, Map<String, Chat>> chats = new ConcurrentHashMap<String, Map<String, Chat>>(); public static final ConcurrentHashMap<String, SmackConnection> connections = new ConcurrentHashMap<String, SmackConnection>(); public static void emitDataAll(String event) { for (ArrayList<RestEventSource> arrayList : eventSources.values()) { for (RestEventSource res : arrayList) { res.emitData(event); } } } public static void emitEvent(String event, String target, String data) { try { for (RestEventSource res : eventSources.get(target)) { res.emitEvent(event, data); } } catch (Exception e) {} } public static void emitEvent(String target, String event) { emitData(target, event); } public static void emitData(String target, String event) { try { for (RestEventSource res : eventSources.get(target)) { res.emitData(event); } } catch (Exception e) {} } public static boolean joinRoom(String source, String mGroupChatName, String mNickName) { Log.info("joinRoom " + source + " " + mGroupChatName + " " + mNickName); try { MultiUserChat mMultiUserChat = groupchats.get(source).get(mGroupChatName); SmackConnection connection = connections.get(source); if (mMultiUserChat == null) { mMultiUserChat = new MultiUserChat(connections.get(source).getConnection(), mGroupChatName); mMultiUserChat.addInvitationRejectionListener(connection); groupchats.get(source).put(mGroupChatName, mMultiUserChat); } mMultiUserChat.join(mNickName); return true; } catch (Exception e) { Log.error("joinRoom", e); return false; } } public static boolean leaveRoom(String source, String mGroupChatName) { Log.info("leaveRoom " + source + " " + mGroupChatName); try { MultiUserChat mMultiUserChat = groupchats.get(source).get(mGroupChatName); SmackConnection connection = connections.get(source); mMultiUserChat.removeInvitationRejectionListener(connection); mMultiUserChat.leave(); return true; } catch (Exception e) { Log.error("leaveRoom", e); return false; } } public static boolean sendRoomMessage(String source, String mGroupChatName, String text) { Log.info("sendRoomMessage " + source + " " + mGroupChatName + "\n" + text); try { groupchats.get(source).get(mGroupChatName).sendMessage(text); return true; } catch (Exception e) { Log.error("sendRoomMessage", e); return false; } } public static boolean inviteToRoom(String source, String mGroupChatName, String inviteJid, String reason) { Log.info("sendRoomMessage " + source + " " + mGroupChatName + " " + inviteJid + "\n" + reason); try { groupchats.get(source).get(mGroupChatName).invite(inviteJid, reason); return true; } catch (Exception e) { Log.error("sendRoomMessage", e); return false; } } public static boolean sendXmppMessage(String source, String message) { Log.info("sendXmppMessage " + source + " " + "\n" + message); try { connections.get(source).getConnection().sendRawXmppMessage(message); return true; } catch (Exception e) { Log.error("sendXmppMessage", e); return false; } } public static boolean closeXmpp(String source) { Log.info("closeXmpp " + source); try { connections.get(source).getConnection().disconnect(); return true; } catch (Exception e) { Log.error("closeXmpp", e); return false; } } public static boolean postPresence(String source, String show, String status) { Log.info("setPresence " + source + " " + show + " " + status); try { Presence p = new Presence(Presence.Type.available); if ("offline".equals(show)) { p = new Presence(Presence.Type.unavailable); } else if ("available".equals(show)) { p = new Presence(Presence.Type.available); p.setMode(Presence.Mode.available); } else if ("away".equals(show)) { p = new Presence(Presence.Type.available); p.setMode(Presence.Mode.away); } else if ("chat".equals(show)) { p = new Presence(Presence.Type.available); p.setMode(Presence.Mode.chat); } else if ("dnd".equals(show)) { p = new Presence(Presence.Type.available); p.setMode(Presence.Mode.dnd); } else if ("xa".equals(show)) { p = new Presence(Presence.Type.available); p.setMode(Presence.Mode.xa); } if (status != null) p.setStatus(status); connections.get(source).getConnection().sendPacket(p); return true; } catch (Exception e) { Log.error("setPresence", e); return false; } } public static boolean sendChatMessage(String source, String message, String to) { Log.info("sendChatMessage " + source + " " + to + "\n" + message); try { if (chats.containsKey(source) == false) chats.put(source, new HashMap<String, Chat>()); Chat chat = chats.get(source).get(to); if (chat == null) { chat = connections.get(source).getConnection().getChatManager().createChat(to, null); chats.get(source).put(to, chat); } chat.sendMessage(message); return true; } catch (Exception e) { Log.error("sendChatMessage", e); return false; } } public static RosterEntities getRoster(String source) { List<RosterItemEntity> rosterEntities = new ArrayList<RosterItemEntity>(); try { Roster roster = connections.get(source).getRoster(); Collection<RosterEntry> entries = roster.getEntries(); Log.info("getRoster " + source + " " + entries.size()); for(RosterEntry entry : entries) { Presence presence = roster.getPresence(entry.getUser()); Log.info("Roster entry " + source + " " + entry.getUser() + " " + entry.getName() + " " + presence.getType().name() + " " + presence.getMode() + " " + presence.getStatus()); int entryStatus = 0; if (entry.getType() != null) { if (entry.getType().equals(RosterPacket.ItemType.both)) entryStatus = 3; if (entry.getType().equals(RosterPacket.ItemType.from)) entryStatus = 2; if (entry.getType().equals(RosterPacket.ItemType.none)) entryStatus = 0; if (entry.getType().equals(RosterPacket.ItemType.remove)) entryStatus = -1; if (entry.getType().equals(RosterPacket.ItemType.to)) entryStatus = 1; } RosterItemEntity rosterItemEntity = new RosterItemEntity(entry.getUser(), entry.getName(), entryStatus); List<String> groups = new ArrayList<String>(); for(RosterGroup group : entry.getGroups()) { groups.add(group.getName()); } rosterItemEntity.setGroups(groups); String show = presence.getType().name(); if (presence.getMode() != null) show = presence.getMode().toString(); rosterItemEntity.setStatus(presence.getStatus()); rosterItemEntity.setShow(show); rosterEntities.add(rosterItemEntity); } } catch (Exception e) { Log.error("getRoster", e); return null; } return new RosterEntities(rosterEntities); } public static synchronized void setConnection(Principal principal, SmackConnection connection) { String source = principal.toString(); if (connections.containsKey(source) == false) { connections.put(source, connection); connection.init(); } } @Override protected EventSource newEventSource(final HttpServletRequest req) { Principal principal = req.getUserPrincipal(); String source = principal.toString(); // get authenticated user RestEventSource eventSource = null; Log.info("newEventSource " + source); if (source != null) { RestEventSourceServlet.setConnection(principal, new SmackConnection(principal)); if (eventSources.containsKey(source)) { if (eventSources.get(source).size() > JiveGlobals.getIntProperty("chatapi.eventsource.amount", 5)) { eventSources.get(source).remove(0); } eventSource = new RestEventSource(source); eventSources.get(source).add(eventSource); } else { ArrayList<RestEventSource> arrayList = new ArrayList<RestEventSource>(); eventSource = new RestEventSource(source); arrayList.add(eventSource); eventSources.put(source, arrayList); } } return eventSource; } public class SmackConnection implements MessageListener, ChatManagerListener, PacketListener, InvitationRejectionListener, InvitationListener { private String source; private String token; private XMPPConnection connection; private ChatManager chatManager; private Roster roster; public SmackConnection(Principal principal) { this.source = principal.toString(); this.token = TokenManager.getInstance().retrieveToken(principal); } public void init() { Log.info("SmackConnection: init " + source + " " + token); try { ConnectionConfiguration config = new ConnectionConfiguration("localhost", 0); connection = new XMPPConnection(config); connection.connect(); connection.login(source, token, source + "-" + System.currentTimeMillis()); chatManager = connection.getChatManager(); chatManager.addChatListener(this); MultiUserChat.addInvitationListener(connection, this); PacketFilter filter = new MessageTypeFilter(Message.Type.groupchat); connection.addPacketListener(this, filter); groupchats.put(source, new HashMap<String, MultiUserChat>()); Presence p = new Presence(Presence.Type.available); connection.sendPacket(p); roster = connection.getRoster(); roster.addRosterListener(new RosterListener() { public void entriesAdded(Collection<String> addresses) {} public void entriesDeleted(Collection<String> addresses) {} public void entriesUpdated(Collection<String> addresses) {} public void rosterError(XMPPError error, Packet packet) {} public void presenceChanged(Presence presence) { RestEventSourceServlet.emitEvent("chatapi.presence", source, "{\"type\": \"presence\", \"to\":\"" + presence.getTo() + "\", \"from\":\"" + presence.getFrom() + "\", \"status\":\"" + presence.getStatus() + "\", \"show\": \"" + presence.getMode() + "\"}"); } }); } catch (Exception e) { Log.error("init", e); } } public XMPPConnection getConnection() { return connection; } public Roster getRoster() { return roster; } public void close() { Log.info("SmackConnection: close " + source + " " + token); if (connection != null) { chatManager.removeChatListener(this); connection.removePacketListener(this); connection.disconnect(); } } public void invitationDeclined(String invitee, String reason) { Log.info("invitationDeclined " + invitee + " " + reason); } public void invitationReceived(Connection conn, String room, String inviter, String reason, String password, Message message) { Log.info("invitationReceived " + room + " " + inviter + " " + reason); } public void processMessage(Chat chat, Message message) { Log.info("Received chat message: " + message.getBody()); if (message.getType() == Message.Type.chat) { RestEventSourceServlet.emitEvent("chatapi.chat", source, "{\"type\": \"" + message.getType() + "\", \"to\":\"" + message.getTo() + "\", \"from\":\"" + message.getFrom() + "\", \"body\": \"" + message.getBody() + "\"}"); } } public void processPacket(Packet packet) { Message message = (Message) packet; Log.info("Received groupchat message: " + message.getBody()); if (message.getType() == Message.Type.groupchat) { RestEventSourceServlet.emitEvent("chatapi.muc", source, "{\"type\": \"" + message.getType() + "\", \"to\":\"" + message.getTo() + "\", \"from\":\"" + message.getFrom() + "\", \"body\": \"" + message.getBody() + "\"}"); } } public void chatCreated(final Chat chat, final boolean createdLocally) { Log.info("Chat created: " + chat.getParticipant()); if (chats.containsKey(source) == false) chats.put(source, new HashMap<String, Chat>()); if (chats.get(source).containsKey(chat.getParticipant()) == false) chats.get(source).put(chat.getParticipant(), chat); chat.addMessageListener(this); } } public class RestEventSource implements org.eclipse.jetty.servlets.EventSource { public Emitter emitter; public boolean isClosed = true; private String source; public RestEventSource(String source) { this.source = source; } public void onOpen(Emitter emitter) throws IOException { Log.info("RestEventSource: onOpen " + source); this.emitter = emitter; this.isClosed = false; } public void emitData(String event) { try { if (!isClosed) { // TODO // <message from="lobby@conference.btg199251" to="deleo@btg199251"><x xmlns="http://jabber.org/protocol/muc#user"><decline from="nigels@btg199251"><reason>No thank you</reason></decline></x></message> Log.info("RestEventSource: emitData \n" + event); emitter.data(event); } } catch (Exception e) { Log.error("emitData", e); } } public void emitEvent(String data) { emitData(data); } public void emitEvent(String event, String data) { try { if (!isClosed) { Log.info("RestEventSource: emitEvent " + event + "\n" + data); emitter.event(event, data); } } catch (Exception e) { Log.error("emitData", e); } } public void onClose() { Log.info("RestEventSource: onClose "); isClosed = true; boolean allClosed = true; for (RestEventSource res : eventSources.get(source)) { if (!res.isClosed) allClosed = false; } if (allClosed) { if (connections.containsKey(source)) connections.remove(source).close(); } } } }