Java tutorial
/* * WebTop Services is a Web Application framework developed by Sonicle S.r.l. * Copyright (C) 2014 Sonicle S.r.l. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Sonicle S.r.l. at email address sonicle@sonicle.com * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * Sonicle logo and Sonicle copyright notice. If the display of the logo is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Copyright (C) 2014 Sonicle S.r.l.". */ package com.sonicle.webtop.core.xmpp; import com.sonicle.commons.EnumUtils; import com.sonicle.commons.IdentifierUtils; import com.sonicle.webtop.core.xmpp.packet.OutOfBandData; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.ChatManager; import org.jivesoftware.smack.chat2.IncomingChatMessageListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterListener; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.jivesoftware.smackx.iqlast.LastActivityManager; import org.jivesoftware.smackx.iqlast.packet.LastActivity; import org.jivesoftware.smackx.muc.Affiliate; import org.jivesoftware.smackx.muc.DiscussionHistory; import org.jivesoftware.smackx.muc.InvitationListener; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.MultiUserChatManager; import org.jivesoftware.smackx.muc.Occupant; import org.jivesoftware.smackx.muc.ParticipantStatusListener; import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.muc.SubjectUpdatedListener; import org.jivesoftware.smackx.muc.UserStatusListener; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Seconds; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.util.JidUtil; import org.jxmpp.stringprep.XmppStringprepException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * https://github.com/igniterealtime/jxmpp/tree/master/jxmpp-jid/src/main/java/org/jxmpp/jid * * EntityBareJid -> user@xmpp.org * EntityFullJid -> user@xmpp.org/resource * DomainBareJid -> xmpp.org * DomainFullJid -> xmpp.org/resource * FullJid -> localpart@domain.part/resourcepart * * @author malbinola */ public class XMPPClient { final static Logger logger = (Logger) LoggerFactory.getLogger(XMPPClient.class); public static final String CHAT_DOMAIN_PREFIX = "instant"; public static final String MUC_ROOMCONFIG_ROOMNAME = "muc#roomconfig_roomname"; public static final String MUC_ROOMCONFIG_ROOMDESC = "muc#roomconfig_roomdesc"; public static final String MUC_ROOMCONFIG_ENABLELOGGING = "muc#roomconfig_enablelogging"; public static final String MUC_ROOMCONFIG_CHANGESUBJECT = "muc#roomconfig_changesubject"; public static final String MUC_ROOMCONFIG_ALLOWINVITES = "muc#roomconfig_allowinvites"; public static final String MUC_ROOMCONFIG_ALLOWPM = "muc#roomconfig_allowpm"; public static final String MUC_ROOMCONFIG_MAXUSERS = "muc#roomconfig_maxusers"; public static final String MUC_ROOMCONFIG_PRESENCEBROADCAST = "muc#roomconfig_presencebroadcast"; public static final String MUC_ROOMCONFIG_GETMEMBERLIST = "muc#roomconfig_getmemberlist"; public static final String MUC_ROOMCONFIG_PUBLICROOM = "muc#roomconfig_publicroom"; public static final String MUC_ROOMCONFIG_PERSISTENTROOM = "muc#roomconfig_persistentroom"; public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom"; public static final String MUC_ROOMCONFIG_MEMBERSONLY = "muc#roomconfig_membersonly"; public static final String MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM = "muc#roomconfig_passwordprotectedroom"; public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; public static final String MUC_ROOMCONFIG_WHOIS = "muc#roomconfig_whois"; public static final String MUC_ROOMCONFIG_MAXHISTORYFETCH = "muc#maxhistoryfetch"; public static final String MUC_ROOMCONFIG_ROOMADMINS = "muc#roomconfig_roomadmins"; public static final String MUC_ROOMCONFIG_ROOMOWNERS = "muc#roomconfig_roomowners"; private XMPPTCPConnectionConfiguration config; private final String mucSubDomain; private final EntityFullJid userJid; private Resourcepart userNickname = null; private final AbstractXMPPConnection con; private final XMPPClientListener listener; private final ConversationHistory history; private final XmppsRosterListener rosterListener; private final XmppsICIncomingMessageListener icIncomingMessageListener; private final XmppsMUCInvitationListener mucInvitationListener; private final Map<EntityBareJid, IChat> instantChats = new HashMap<>(); private final Map<EntityBareJid, GChat> groupChats = new HashMap<>(); private int loginCount = 0; private final AtomicBoolean isDisconnecting = new AtomicBoolean(false); private Presence lastPresence = null; private ConcurrentHashMap<String, EntityBareJid> cacheInstantChatToFriend = new ConcurrentHashMap<>(); static { ProviderManager.addExtensionProvider(OutOfBandData.ELEMENT_NAME, OutOfBandData.NAMESPACE, new OutOfBandData.Provider()); } public XMPPClient(XMPPTCPConnectionConfiguration.Builder builder, String mucSubdomain, String nickname, XMPPClientListener listener) { this(builder, mucSubdomain, nickname, listener, null); } public XMPPClient(XMPPTCPConnectionConfiguration.Builder builder, String mucSubdomain, String nickname, XMPPClientListener listener, ConversationHistory history) { builder.setSendPresence(false); this.config = builder.build(); this.mucSubDomain = mucSubdomain; this.userJid = JidCreate.entityFullFrom(XMPPHelper.asLocalpart(config.getUsername()), config.getXMPPServiceDomain(), config.getResource()); this.userNickname = XMPPHelper.asResourcepart(nickname); this.con = new XMPPTCPConnection(config); this.listener = listener; this.history = history; this.rosterListener = new XmppsRosterListener(); this.icIncomingMessageListener = new XmppsICIncomingMessageListener(); this.mucInvitationListener = new XmppsMUCInvitationListener(); } public boolean isConnected() { synchronized (con) { return con.isConnected(); } } public boolean isAuthenticated() { synchronized (con) { return con.isAuthenticated(); } } public void disconnect() throws XMPPClientException { synchronized (con) { if (!isConnected() && (loginCount == 0)) return; checkConnection(); try { isDisconnecting.set(true); con.sendStanza(createOfflinePresence()); internalLogout(); con.disconnect(); isDisconnecting.set(false); } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } } public EntityFullJid getUserJid() { return this.userJid; } public String getUserNickame() { return this.userNickname.toString(); } public void updatePresence(PresenceStatus presenceStatus, String statusText) throws XMPPClientException { checkAuthentication(); Presence presence = new Presence(PresenceStatus.presenceType(presenceStatus)); //presence.setPriority(24); Presence.Mode mode = PresenceStatus.presenceMode(presenceStatus); if (mode != null) presence.setMode(mode); if (statusText != null) presence.setStatus(statusText); try { internalSendPresence(presence); } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } public List<Friend> listFriends() throws XMPPClientException { checkAuthentication(); try { final Roster roster = getRoster(); checkRosterLoaded(roster); ArrayList<Friend> friends = new ArrayList<>(); for (RosterEntry entry : roster.getEntries()) { final EntityBareJid friendJid = entry.getJid().asEntityBareJidOrThrow(); final String instantChat = generateChatBareJid(friendJid).toString(); cacheInstantChatToFriend.putIfAbsent(instantChat, friendJid); FriendPresence presence = getFriendPresence(friendJid); friends.add(new Friend(entry, presence, instantChat)); } return friends; } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } public FriendPresence getFriendPresence(String friendBareJid) throws XMPPClientException { return getFriendPresence(XMPPHelper.asEntityBareJid(friendBareJid)); } public FriendPresence getFriendPresence(EntityBareJid friend) throws XMPPClientException { checkAuthentication(); Roster roster = Roster.getInstanceFor(con); Presence presence = roster.getPresence(friend.asBareJid()); return (presence != null) ? new FriendPresence(presence, generateChatBareJid(friend).toString()) : null; } public String getFriendNickname(EntityBareJid friend) throws XMPPClientException { return getFriendNickname(friend, true); } public String getFriendNickname(EntityBareJid friend, boolean guessIfNull) throws XMPPClientException { checkAuthentication(); Roster roster = Roster.getInstanceFor(con); return getRosterEntryNickname(roster, friend, guessIfNull); } public LastActivity getLastActivity(Jid friend) throws XMPPClientException { checkAuthentication(); try { final LastActivityManager lastActivityManager = LastActivityManager.getInstanceFor(con); return lastActivityManager.getLastActivity(friend); } catch (SmackException | XMPPException | InterruptedException ex) { throw new XMPPClientException(ex); } } public EntityBareJid generateChatBareJid(String withUserBareJid) { return createInstantChatJid(withUserBareJid); } public EntityBareJid generateChatBareJid(EntityBareJid withUser) { return createInstantChatJid(withUser); } public List<ChatRoom> listChats() throws XMPPClientException { checkAuthentication(); ArrayList<ChatRoom> chats = new ArrayList<>(); synchronized (instantChats) { for (IChat chatObj : instantChats.values()) { chats.add(chatObj.getChatRoom()); } } synchronized (groupChats) { for (GChat chatObj : groupChats.values()) { chats.add(chatObj.getChatRoom()); } } return chats; } public EntityBareJid newInstantChat(EntityBareJid withFriend) throws XMPPClientException { checkAuthentication(); try { final Roster roster = getRoster(); checkRosterLoaded(roster); final EntityBareJid chatJid = createInstantChatJid(withFriend); final String withUserNick = getRosterEntryNickname(roster, withFriend, true); synchronized (instantChats) { IChat chatObj = instantChats.get(chatJid); if (chatObj == null) { final ChatManager chatMgr = getChatManager(); final Chat chat = chatMgr.chatWith(withFriend); chatObj = doAddChat(false, true, chatJid, userJid.asEntityBareJid(), withUserNick, null, withFriend, chat); } } return chatJid; } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } public EntityBareJid newGroupChat(String name, List<EntityBareJid> withFriends) throws XMPPClientException { checkAuthentication(); final EntityBareJid myJid = userJid.asEntityBareJid(); final EntityBareJid chatJid = createGroupChatJid(); synchronized (groupChats) { GChat chatObj = groupChats.get(chatJid); if (chatObj == null) { try { final MultiUserChatManager muChatMgr = getMUChatManager(); final MultiUserChat muc = muChatMgr.getMultiUserChat(chatJid); muc.create(XMPPHelper.asResourcepart(name)); Form form = muc.getConfigurationForm(); Form answerForm = form.createAnswerForm(); configureMucForm(answerForm, chatJid, name, myJid); muc.sendConfigurationForm(answerForm); muc.changeSubject(name); muc.join(userNickname); chatObj = doAddGroupChat(false, true, chatJid, myJid, name, null, muc); for (EntityBareJid withFriend : withFriends) { muc.invite(withFriend, name); } } catch (SmackException | XMPPException | InterruptedException ex) { throw new XMPPClientException(ex); } } } return chatJid; } public ChatRoom getChat(String chatJid) throws XMPPClientException { return getChat(XMPPHelper.asEntityBareJid(chatJid)); } public ChatRoom getChat(EntityBareJid chatJid) throws XMPPClientException { checkAuthentication(); if (isInstantChat(chatJid)) { IChat chatObj = instantChats.get(chatJid); return (chatObj != null) ? chatObj.getChatRoom() : null; } else { GChat chatObj = groupChats.get(chatJid); return (chatObj != null) ? chatObj.getChatRoom() : null; } } public void existChat(String chatJid) throws XMPPClientException { existChat(XMPPHelper.asEntityBareJid(chatJid)); } public boolean existChat(EntityBareJid chatJid) throws XMPPClientException { checkAuthentication(); if (isInstantChat(chatJid)) { return instantChats.containsKey(chatJid); } else { return groupChats.containsKey(chatJid); } } public void forgetChat(String chatJid) throws XMPPClientException { forgetChat(XMPPHelper.asEntityBareJid(chatJid)); } public void forgetChat(EntityBareJid chatJid) throws XMPPClientException { checkAuthentication(); if (isInstantChat(chatJid)) { synchronized (instantChats) { if (instantChats.containsKey(chatJid)) doRemoveChat(chatJid); } } else { try { synchronized (groupChats) { if (groupChats.containsKey(chatJid)) doRemoveGroupChat(chatJid); } } catch (XMPPException | SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } } public List<ChatMember> getChatMembers(String chatJid) throws XMPPClientException { return getChatMembers(XMPPHelper.asEntityBareJid(chatJid)); } public List<ChatMember> getChatMembers(EntityBareJid chatJid) throws XMPPClientException { ArrayList<ChatMember> members = new ArrayList<>(); checkAuthentication(); final EntityBareJid myJid = userJid.asEntityBareJid(); if (isInstantChat(chatJid)) { synchronized (instantChats) { IChat chatObj = instantChats.get(chatJid); if (chatObj == null) return null; try { final Roster roster = getRoster(); checkRosterLoaded(roster); final String withNickname = getRosterEntryNickname(roster, chatObj.getChatRoom().getWithJid(), true); if (chatObj.getChatRoom().isOwner(myJid)) { members.add(new ChatMember(myJid, MemberRole.OWNER, userNickname.toString())); members.add(new ChatMember(chatObj.getChatRoom().getWithJid(), MemberRole.PARTECIPANT, withNickname)); } else { members.add( new ChatMember(chatObj.getChatRoom().getWithJid(), MemberRole.OWNER, withNickname)); members.add(new ChatMember(myJid, MemberRole.PARTECIPANT, userNickname.toString())); } } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } return members; } } else { synchronized (groupChats) { GChat chatObj = groupChats.get(chatJid); if (chatObj == null) return null; try { final MultiUserChat muc = chatObj.getRawChat(); final Roster roster = getRoster(); checkRosterLoaded(roster); for (Affiliate aff : muc.getOwners()) { final EntityBareJid jid = aff.getJid().asEntityBareJidOrThrow(); final String nick = getEntryNickname(roster, jid, true); members.add(new ChatMember(jid, MemberRole.OWNER, nick)); } for (Affiliate aff : muc.getMembers()) { final EntityBareJid jid = aff.getJid().asEntityBareJidOrThrow(); final String nick = getEntryNickname(roster, jid, true); members.add(new ChatMember(jid, MemberRole.PARTECIPANT, nick)); } return members; } catch (XMPPException | SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } } } public FriendPresence getChatPresence(String chatJid) throws XMPPClientException { return getChatPresence(XMPPHelper.asEntityBareJid(chatJid)); } public FriendPresence getChatPresence(EntityBareJid chatJid) throws XMPPClientException { checkAuthentication(); if (isInstantChat(chatJid)) { synchronized (instantChats) { EntityBareJid presenceUser = null; IChat chatObj = instantChats.get(chatJid); if (chatObj == null) { presenceUser = cacheInstantChatToFriend.get(chatJid.toString()); if (presenceUser == null) return null; } else { presenceUser = chatObj.getChatRoom().getWithJid(); } return getFriendPresence(presenceUser); } } else { throw new UnsupportedOperationException("Feature not available for a groupChat"); } } public ChatMessage sendTextMessage(String chatBareJid, String text) throws XMPPClientException { return sendTextMessage(XMPPHelper.asEntityBareJid(chatBareJid), text); } public ChatMessage sendTextMessage(EntityBareJid chatJid, String text) throws XMPPClientException { Message message = new Message(); message.setBody(text); return internalSendMessage(chatJid, message); } public ChatMessage sendFileMessage(String chatBareJid, String name, String url, String mediaType, long size) throws XMPPClientException { return sendFileMessage(XMPPHelper.asEntityBareJid(chatBareJid), name, url, mediaType, size); } public ChatMessage sendFileMessage(EntityBareJid chatJid, String name, String url, String mediaType, long size) throws XMPPClientException { Message message = new Message(); message.setBody(name); OutOfBandData oobData = new OutOfBandData(url, mediaType, size); message.addExtension(oobData); return internalSendMessage(chatJid, message); } private ChatMessage internalSendMessage(EntityBareJid chatJid, Message message) throws XMPPClientException { checkAuthentication(); final EntityBareJid myJid = userJid.asEntityBareJid(); final String myResource = XMPPHelper.asResourcepartString(userJid.getResourceOrNull()); if (isInstantChat(chatJid)) { synchronized (instantChats) { IChat chatObj = instantChats.get(chatJid); if (chatObj == null) { EntityBareJid withUser = cacheInstantChatToFriend.get(chatJid.toString()); if (withUser != null) { newInstantChat(withUser); chatObj = instantChats.get(chatJid); } } if (chatObj != null) { try { final DateTime now = ChatMessage.nowTimestamp(); chatObj.getRawChat().send(message); if (logger.isTraceEnabled()) { logger.trace("Message sent [{}, {}]", now, StringUtils.abbreviate(message.getBody(), 20)); } ChatMessage chatMessage = new ChatMessage(chatJid, myJid, myResource, userNickname.toString(), now, now, message); // TODO: threadify this? try { listener.onChatRoomMessageSent(chatObj.getChatRoom(), chatMessage); } catch (Throwable t) { logger.error("Listener error", t); } return chatMessage; } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } } } else { synchronized (groupChats) { GChat chatObj = groupChats.get(chatJid); if (chatObj != null) { try { final DateTime now = ChatMessage.nowTimestamp(); chatObj.getRawChat().sendMessage(message); if (logger.isTraceEnabled()) { logger.trace("Message sent [{}, {}]", now, StringUtils.abbreviate(message.getBody(), 20)); } ChatMessage chatMessage = new ChatMessage(chatJid, myJid, myResource, userNickname.toString(), now, now, message); // TODO: threadify this? try { listener.onChatRoomMessageSent(chatObj.getChatRoom(), chatMessage); } catch (Throwable t) { logger.error("Listener error", t); } return chatMessage; } catch (SmackException | InterruptedException ex) { throw new XMPPClientException(ex); } } } } throw new XMPPClientException("Chat not found. Please create a chat before try to send messages in it!"); } private IChat doAddChat(boolean skipListeners, boolean self, EntityBareJid chatJid, EntityBareJid ownerJid, String name, DateTime lastSeenActivity, EntityBareJid withJid, Chat chat) { return doAddChat(skipListeners, self, new InstantChatRoom(chatJid, ownerJid, name, lastSeenActivity, withJid), chat); } private IChat doAddChat(boolean skipListeners, boolean self, InstantChatRoom chatRoom, Chat chat) { logger.debug("Adding instant chat [{}]", chatRoom.getChatJid().toString()); IChat chatObj = new IChat(chatRoom, chat); instantChats.put(chatRoom.getChatJid(), chatObj); if (!skipListeners) { try { listener.onChatRoomAdded(chatObj.getChatRoom(), getChatOwnerNick(chatRoom), self); } catch (Throwable t) { logger.error("Listener error", t); } } return chatObj; } private String getChatOwnerNick(ChatRoom chatRoom) throws XMPPClientException { if (XMPPHelper.jidEquals(chatRoom.getOwnerJid(), getUserJid())) { return getUserNickame(); } else { return getFriendNickname(chatRoom.getOwnerJid(), true); } } private void doRemoveChat(EntityBareJid chatJid) { logger.debug("Removing instant chat [{}]", chatJid.toString()); IChat chatObj = instantChats.remove(chatJid); try { final ChatRoom chatRoom = chatObj.getChatRoom(); listener.onChatRoomRemoved(chatObj.getChatRoom().getChatJid(), chatRoom.getName(), chatRoom.getOwnerJid(), getChatOwnerNick(chatRoom)); } catch (Throwable t) { logger.error("Listener error", t); } } private GChat doAddGroupChat(boolean skipListeners, boolean self, EntityBareJid chatJid, EntityBareJid ownerJid, String name, DateTime lastSeenActivity, MultiUserChat chat) { return doAddGroupChat(skipListeners, self, new GroupChatRoom(chatJid, ownerJid, name, lastSeenActivity), chat); } private GChat doAddGroupChat(boolean skipListeners, boolean self, GroupChatRoom chatRoom, MultiUserChat chat) { logger.debug("Creating group chat [{}]", chatRoom.getChatJid().toString()); GChat chatObj = new GChat(chatRoom); groupChats.put(chatRoom.getChatJid(), chatObj); if (!skipListeners) { try { listener.onChatRoomAdded(chatObj.getChatRoom(), getChatOwnerNick(chatRoom), self); } catch (Throwable t) { logger.error("Listener error", t); } } chat.addSubjectUpdatedListener(chatObj); chat.addMessageListener(chatObj); chat.addUserStatusListener(chatObj); chat.addParticipantStatusListener(chatObj); return chatObj; } private void doRemoveGroupChat(EntityBareJid chatJid) throws XMPPException, SmackException, InterruptedException { logger.debug("Removing group chat [{}]", chatJid.toString()); GChat chatObj = groupChats.remove(chatJid); MultiUserChat chat = chatObj.getRawChat(); chat.removeSubjectUpdatedListener(chatObj); chat.removeMessageListener(chatObj); chat.removeUserStatusListener(chatObj); chat.removeParticipantStatusListener(chatObj); if (XMPPHelper.jidEquals(chatObj.getChatRoom().getOwnerJid(), getUserJid().asEntityBareJid())) { chat.destroy("Cleanup", null); } try { final ChatRoom chatRoom = chatObj.getChatRoom(); listener.onChatRoomRemoved(chatObj.getChatRoom().getChatJid(), chatRoom.getName(), chatRoom.getOwnerJid(), getChatOwnerNick(chatRoom)); } catch (Throwable t) { logger.error("Listener error", t); } } private Presence createOfflinePresence() { Presence presence = null; if (this.lastPresence != null) { presence = this.lastPresence.clone(); presence.setType(Presence.Type.unavailable); } else { presence = new Presence(Presence.Type.unavailable); } presence.setMode(Presence.Mode.away); //presence.setPriority(24); return presence; } private void checkRosterLoaded(final Roster roster) throws SmackException, InterruptedException { if (!roster.isLoaded()) roster.reloadAndWait(); } private String chatDomain(String subname) { return subname + "." + userJid.getDomain().toString(); } private EntityBareJid createInstantChatJid(EntityBareJid friend) { return (friend == null) ? null : createInstantChatJid(friend.toString()); } private EntityBareJid createInstantChatJid(String friendBareJid) { return buildChatJid(DigestUtils.md5Hex(friendBareJid), chatDomain(CHAT_DOMAIN_PREFIX)); } private EntityBareJid createGroupChatJid() { return buildChatJid(IdentifierUtils.getUUIDTimeBased(true), chatDomain(mucSubDomain)); } private EntityBareJid buildChatJid(String local, String domain) { final String s = local + "@" + domain; try { return JidCreate.bareFrom(s).asEntityBareJidIfPossible(); } catch (XmppStringprepException ex) { logger.error("Error building MUC jid [{}]", ex, s); return null; } } private EntityBareJid createOccupantJid(String nick) { return JidCreate.entityBareFrom(XMPPHelper.asLocalpart(nick), userJid.getDomain()); } private Roster getRoster() { return Roster.getInstanceFor(con); } private ChatManager getChatManager() { return ChatManager.getInstanceFor(con); } private MultiUserChatManager getMUChatManager() { return MultiUserChatManager.getInstanceFor(con); } private String getEntryNickname(Roster roster, BareJid jid, boolean guessIfNull) { if (XMPPHelper.jidBareEquals(jid, userJid)) { return userNickname.toString(); } else { return getRosterEntryNickname(roster, jid, guessIfNull); } } private String getRosterEntryNickname(Roster roster, BareJid jid, boolean guessIfNull) { RosterEntry entry = roster.getEntry(jid); String nick = (entry != null) ? entry.getName() : null; if (StringUtils.isBlank(nick)) { return guessIfNull ? XMPPHelper.buildGuessedString(jid.toString()) : null; } else { return nick; } } private void configureMucForm(Form answerForm, EntityBareJid chatJid, String chatName, EntityBareJid ownerJid) { // Apply defaults for all form fields for (FormField field : answerForm.getFields()) { if (field.getType() == FormField.Type.hidden || StringUtils.isBlank(field.getVariable())) { continue; } answerForm.setDefaultAnswer(field.getVariable()); } // Natural-Language Room Name answerForm.setAnswer(MUC_ROOMCONFIG_ROOMNAME, chatName); // Short Description of Room answerForm.setAnswer(MUC_ROOMCONFIG_ROOMDESC, ""); // Enable Public Logging? if (answerForm.hasField(MUC_ROOMCONFIG_ENABLELOGGING)) { answerForm.setAnswer(MUC_ROOMCONFIG_ENABLELOGGING, false); } // Allow Occupants to Change Subject? if (answerForm.hasField(MUC_ROOMCONFIG_CHANGESUBJECT)) { answerForm.setAnswer(MUC_ROOMCONFIG_CHANGESUBJECT, false); } // Allow Occupants to Invite Others? if (answerForm.hasField(MUC_ROOMCONFIG_ALLOWINVITES)) { answerForm.setAnswer(MUC_ROOMCONFIG_ALLOWINVITES, false); } // Who Can Send Private Messages? (anyone, participants, moderators, none) if (answerForm.hasField(MUC_ROOMCONFIG_ALLOWPM)) { answerForm.setAnswer(MUC_ROOMCONFIG_ALLOWPM, XMPPHelper.asFormListSingleType("none")); } // Maximum Number of Occupants (10, 20, 30, 50, 100, none) if (answerForm.hasField(MUC_ROOMCONFIG_MAXUSERS)) { answerForm.setAnswer(MUC_ROOMCONFIG_MAXUSERS, XMPPHelper.asFormListSingleType("50")); } // Roles for which Presence is Broadcasted (moderator, participant, visitor) /* if (answerForm.hasField(MUC_ROOMCONFIG_PRESENCEBROADCAST)) { answerForm.setAnswer(MUC_ROOMCONFIG_PRESENCEBROADCAST, XMPPHelper.asFormListMultiType("participant")); } */ // Roles and Affiliations that May Retrieve Member List (moderator, participant, visitor) if (answerForm.hasField(MUC_ROOMCONFIG_GETMEMBERLIST)) { answerForm.setAnswer(MUC_ROOMCONFIG_GETMEMBERLIST, XMPPHelper.asFormListMultiType("participant")); } // Make Room Publicly Searchable? if (answerForm.hasField(MUC_ROOMCONFIG_PUBLICROOM)) { answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICROOM, false); } // Make Room Persistent? if (answerForm.hasField(MUC_ROOMCONFIG_PERSISTENTROOM)) { answerForm.setAnswer(MUC_ROOMCONFIG_PERSISTENTROOM, true); } // Make Room Moderated? if (answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM)) { answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, false); } // Make Room Members Only? if (answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY)) { answerForm.setAnswer(MUC_ROOMCONFIG_MEMBERSONLY, true); } // Password Required for Entry? if (answerForm.hasField(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM)) { answerForm.setAnswer(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM, false); } // Who May Discover Real JIDs? (moderators, anyone) if (answerForm.hasField(MUC_ROOMCONFIG_WHOIS)) { answerForm.setAnswer(MUC_ROOMCONFIG_WHOIS, XMPPHelper.asFormListSingleType("anyone")); } // Maximum Number of History Messages Returned by Room if (answerForm.hasField(MUC_ROOMCONFIG_MAXHISTORYFETCH)) { answerForm.setAnswer(MUC_ROOMCONFIG_MAXHISTORYFETCH, "50"); } // Additional Room Admins // Room Owners if (answerForm.hasField(MUC_ROOMCONFIG_ROOMOWNERS)) { Set<EntityBareJid> owners = new HashSet<>(); owners.add(ownerJid); answerForm.setAnswer(MUC_ROOMCONFIG_ROOMOWNERS, JidUtil.toStringList(owners)); } if (logger.isTraceEnabled()) { logger.trace("Dumping MUC configuration..."); for (FormField field : answerForm.getFields()) { logger.trace("{}: {}", field.getVariable(), field.getValues()); } } } private boolean joinMuc(final MultiUserChat muc, final DateTime lastSeenActivity) { try { logger.debug("Joining group chat [{}, {}]", muc.getRoom().toString(), lastSeenActivity); DiscussionHistory mucHistory = new DiscussionHistory(); if (lastSeenActivity != null) { DateTime now = new DateTime(DateTimeZone.UTC); Seconds seconds = Seconds.secondsBetween(lastSeenActivity.withZone(DateTimeZone.UTC), now); mucHistory.setSeconds(Math.abs(seconds.getSeconds())); } muc.join(userNickname, null, mucHistory, SmackConfiguration.getDefaultReplyTimeout()); return true; } catch (SmackException | XMPPException | InterruptedException ex) { logger.error("Error joining group chat", ex); return false; } } private GuessMucFromResult guessMucFrom(Roster roster, MultiUserChat muc, EntityFullJid rawFrom) { EntityFullJid fromJid = null; String fromNick = null; Occupant occupant = muc.getOccupant(rawFrom); if (occupant != null) { fromJid = occupant.getJid().asEntityFullJidIfPossible(); fromNick = getRosterEntryNickname(roster, occupant.getJid().asBareJid(), false); if (StringUtils.isBlank(fromNick)) { fromNick = XMPPHelper.buildGuessedString(occupant.getJid().asBareJid()); } } else { // Due to occupant is not available, we assume that nick in message // sender is reconducible to a domain user... final EntityBareJid occupantBareJid = createOccupantJid(XMPPHelper.asResourcepartString(rawFrom)); fromJid = JidCreate.entityFullFrom(occupantBareJid, XMPPHelper.asResourcepart(rawFrom.asEntityBareJidString())); fromNick = getRosterEntryNickname(roster, occupantBareJid, false); if (StringUtils.isBlank(fromNick)) { fromNick = XMPPHelper .buildGuessedString(XMPPHelper.asResourcepartString(rawFrom.getResourceOrNull())); } } return new GuessMucFromResult(fromJid, fromNick); } private DateTime retrieveDelayInformation(Message message) { DelayInformation dinfo = DelayInformation.from(message); if (dinfo != null) { return new DateTime(dinfo.getStamp(), DateTimeZone.UTC); } else { return null; } } private void internalLogin() throws SmackException, XMPPException, InterruptedException, IOException { final boolean restoreHistory = (loginCount == 0); loginCount++; final Roster roster = getRoster(); roster.addRosterListener(rosterListener); final ChatManager chatMgr = getChatManager(); chatMgr.addIncomingListener(icIncomingMessageListener); final MultiUserChatManager muChatMgr = getMUChatManager(); muChatMgr.addInvitationListener(mucInvitationListener); // Restore instant (one-to-one) chat rooms from history if (restoreHistory && (history != null)) { for (ChatRoom chat : history.getChats()) { if (chat instanceof InstantChatRoom) { final InstantChatRoom chatObj = (InstantChatRoom) chat; doAddChat(true, true, chatObj, null); } } } con.login(); checkRosterLoaded(roster); // Restore previous multi chat rooms from history if (restoreHistory && (history != null)) { for (ChatRoom chat : history.getChats()) { if (chat instanceof GroupChatRoom) { final GroupChatRoom chatRoom = (GroupChatRoom) chat; RoomInfo info = getRoomInfo(muChatMgr, chat.getChatJid()); if (info != null) { final MultiUserChat muc = muChatMgr.getMultiUserChat(chat.getChatJid()); doAddGroupChat(true, true, chatRoom, muc); } else { logger.debug("Group chat unavailable [{}, {}]", chatRoom.getChatJid(), chatRoom.getName()); try { listener.onChatRoomUnavailable(chatRoom, getChatOwnerNick(chatRoom)); } catch (Throwable t) { logger.error("Listener error", t); } } } } } for (IChat chatObj : instantChats.values()) { final EntityBareJid withJid = chatObj.getChatRoom().getWithJid().asEntityBareJid(); String nick = chatObj.getChatRoom().getName(); String friendNick = getRosterEntryNickname(roster, withJid, false); if (friendNick != null) nick = friendNick; if (StringUtils.isBlank(nick)) nick = XMPPHelper.buildGuessedString(withJid.toString()); if (!StringUtils.equals(chatObj.getChatRoom().getName(), nick)) { // Nick updated } } for (GChat chatObj : groupChats.values()) { final MultiUserChat muc = chatObj.getRawChat(); if (!muc.isJoined()) { boolean joined = joinMuc(muc, chatObj.getChatRoom().getLastSeenActivity()); if (joined) logger.debug("Group chat re-joined [{}]", muc.getNickname()); } } if (this.lastPresence != null) { internalSendPresence(this.lastPresence); } } private void internalSendPresence(Presence presence) throws SmackException, InterruptedException { this.lastPresence = presence; con.sendStanza(presence); } private RoomInfo getRoomInfo(MultiUserChatManager mucManager, EntityBareJid chatJid) throws SmackException, XMPPException, InterruptedException, IOException { try { return mucManager.getRoomInfo(chatJid); } catch (XMPPException.XMPPErrorException ex) { if (ex.getXMPPError().getCondition() == XMPPError.Condition.item_not_found) { return null; } else { throw ex; } } catch (SmackException | XMPPException | InterruptedException ex) { throw ex; } } private void internalLogout() { final MultiUserChatManager muChatMgr = getMUChatManager(); muChatMgr.removeInvitationListener(mucInvitationListener); final ChatManager chatMgr = getChatManager(); chatMgr.removeListener(icIncomingMessageListener); final Roster roster = getRoster(); roster.removeRosterListener(rosterListener); } private void checkAuthentication() throws XMPPClientException { synchronized (con) { checkConnection(); try { if (!con.isAuthenticated()) internalLogin(); } catch (SmackException | XMPPException | InterruptedException | IOException ex) { logger.error("Unable to login", ex); throw new XMPPClientException(ex); } } } private void checkConnection() throws XMPPClientException { synchronized (con) { try { if (!con.isConnected()) con.connect(); } catch (SmackException | XMPPException | InterruptedException | IOException ex) { logger.error("Unable to establish a connection", ex); throw new XMPPClientException(ex); } } } private class XmppsRosterListener implements RosterListener { @Override public void entriesAdded(Collection<Jid> clctn) { if (isDisconnecting.get()) return; try { logger.debug("Entry added [{}]", clctn); listener.onFriendsAdded(clctn); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void entriesUpdated(Collection<Jid> clctn) { if (isDisconnecting.get()) return; try { logger.debug("Entry updated [{}]", clctn); listener.onFriendsUpdated(clctn); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void entriesDeleted(Collection<Jid> clctn) { if (isDisconnecting.get()) return; try { logger.debug("Entry deleted [{}]", clctn); listener.onFriendsDeleted(clctn); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void presenceChanged(Presence prsnc) { if (isDisconnecting.get()) return; final EntityBareJid friendJid = prsnc.getFrom().asEntityBareJidIfPossible(); final FriendPresence presence = new FriendPresence(prsnc, generateChatBareJid(friendJid).toString()); logger.debug("Presence changed [{}, {}]", EnumUtils.toSerializedName(presence.getPresenceStatus()), presence.getFriendFullJid()); try { listener.onFriendPresenceChanged(prsnc.getFrom(), presence, getFriendPresence(friendJid)); } catch (Throwable t) { logger.error("Listener error", t); } } } private class XmppsICIncomingMessageListener implements IncomingChatMessageListener { @Override public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) { if (isDisconnecting.get()) return; try { final Roster roster = getRoster(); checkRosterLoaded(roster); final EntityBareJid chatJid = createInstantChatJid(from); final String fromNick = getRosterEntryNickname(roster, from, true); IChat chatObj = null; synchronized (instantChats) { chatObj = instantChats.get(chatJid); if (chatObj == null) { chatObj = doAddChat(false, false, chatJid, from, fromNick, ChatMessage.nowTimestamp(), from, chat); } } final DateTime delay = retrieveDelayInformation(message); final DateTime now = ChatMessage.nowTimestamp(); chatObj.getChatRoom().setLastSeenActivity(now); DateTime msgTs, delivTs; if (delay != null) { msgTs = delay; delivTs = now; } else { msgTs = now; delivTs = now; } if (logger.isTraceEnabled()) { logger.trace("Message received [{}, {}, {}]", msgTs, delivTs, StringUtils.abbreviate(message.getBody(), 20)); } try { ChatMessage chatMessage = new ChatMessage(chatJid, message.getFrom(), fromNick, msgTs, delivTs, message); listener.onChatRoomMessageReceived(chatObj.getChatRoom(), chatMessage); } catch (Throwable t) { logger.error("Listener error", t); } } catch (SmackException | InterruptedException ex) { logger.error("IncomingChatMessageListener error", ex); } } } private class XmppsMUCInvitationListener implements InvitationListener { @Override public void invitationReceived(XMPPConnection xmppc, MultiUserChat multiUserChat, EntityJid inviterJid, String reason, String password, Message message, MUCUser.Invite invitation) { if (isDisconnecting.get()) return; final MultiUserChatManager muChatMgr = getMUChatManager(); final EntityBareJid chatJid = multiUserChat.getRoom(); final String name = reason; logger.debug("Group chat invitation received [{}, {}]", chatJid.toString(), inviterJid.toString()); MultiUserChat muc = muChatMgr.getMultiUserChat(chatJid); GChat chatObj = null; synchronized (groupChats) { chatObj = groupChats.get(chatJid); if (chatObj == null) { chatObj = doAddGroupChat(false, false, chatJid, inviterJid.asEntityBareJid(), name, null, muc); } if (!muc.isJoined()) { boolean joined = joinMuc(muc, null); if (joined) logger.debug("Group chat re-joined [{}]", muc.getNickname()); } else { logger.warn("Unexpected! Group chat already joined [{}]", multiUserChat.getNickname()); } } } } private class IChat { private final InstantChatRoom chatRoom; private Chat rawChat; public IChat(InstantChatRoom chatRoom) { this(chatRoom, null); } public IChat(InstantChatRoom chatRoom, Chat rawChat) { this.chatRoom = chatRoom; this.rawChat = rawChat; } public InstantChatRoom getChatRoom() { return chatRoom; } public synchronized Chat getRawChat() { if (rawChat == null) { rawChat = getChatManager().chatWith(chatRoom.getWithJid()); } return rawChat; } } private class GChat implements SubjectUpdatedListener, MessageListener, UserStatusListener, ParticipantStatusListener { private final GroupChatRoom chatRoom; public GChat(GroupChatRoom chatRoom) { this.chatRoom = chatRoom; } public GroupChatRoom getChatRoom() { return chatRoom; } public MultiUserChat getRawChat() { return getMUChatManager().getMultiUserChat(chatRoom.getChatJid()); } @Override public void subjectUpdated(String subject, EntityFullJid from) { chatRoom.setName(subject); try { listener.onChatRoomUpdated(chatRoom, false); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void processMessage(Message message) { final Roster roster = getRoster(); if (message.getSubjects().size() > 0) { logger.warn("Subject update {}", message); } else if (!StringUtils.isBlank(message.getBody())) { boolean skipListener = false; // In groupchat the sender jid is not directly reconducible to // an internal roster jid: it is like to "chat@conference.domain.tld/nick" // Instead of above, we want fromJid like this: "user@domain.tld/resource" // NB: Now we assume that the nick provided is reconducible to a real user! final Jid rawFromJid = message.getFrom(); final GuessMucFromResult guessedFrom = guessMucFrom(roster, getRawChat(), rawFromJid.asEntityFullJidOrThrow()); // Ignore messages coming from myself if (XMPPHelper.jidEquals(guessedFrom.jid, getUserJid())) skipListener = true; final DateTime delay = retrieveDelayInformation(message); if (!skipListener) { if (delay != null && history != null) { final HashSet<String> idsInHistory = history.getStanzaIds(getChatRoom().getChatJid()); if (idsInHistory != null) { skipListener = idsInHistory.contains(message.getStanzaId()); } } } final DateTime now = ChatMessage.nowTimestamp(); chatRoom.setLastSeenActivity(now); DateTime msgTs, delivTs; if (delay != null) { msgTs = delay; delivTs = now; } else { msgTs = now; delivTs = now; } if (logger.isTraceEnabled()) { logger.trace("Message received [{}, {}, {}]", msgTs, delivTs, StringUtils.abbreviate(message.getBody(), 20)); } if (!skipListener) { try { ChatMessage chatMessage = new ChatMessage(chatRoom.getChatJid(), guessedFrom.jid.asEntityBareJidIfPossible(), XMPPHelper.asResourcepartString(guessedFrom.jid.getResourceOrNull()), guessedFrom.nick, msgTs, delivTs, message); listener.onChatRoomMessageReceived(chatRoom, chatMessage); } catch (Throwable t) { logger.error("Listener error", t); } } // In groupchat the sender jid is not directly reconducible to // an internal roster jid: it is like to "chat@conference.domain.top/nick" // Instead of above, we want fromJid like this: "user@domain.top/resource" //final Jid rawFromJid = message.getFrom(); // We need to get firstly the occupant and then guess the real jid // and its nickname from internal roster. //final Occupant occupant = getRawChat().getOccupant(rawFromJid.asEntityFullJidOrThrow()); //final EntityFullJid fromJid = occupant.getJid().asEntityFullJidIfPossible(); //final String fromNick = guessOccupantNick(roster, occupant); //final EntityFullJid fromJid = guessOccupantJid(getRawChat(), rawFromJid.asEntityFullJidOrThrow()); //final String fromNick = guessOccupantNick(roster, getRawChat(), rawFromJid.asEntityFullJidOrThrow()); //final Jid fromJid = message.getFrom(); //final String fromNick = getRosterEntryNickname(roster, fromJid.asBareJid(), true); /* if (!XMPPHelper.jidEquals(fromJid, getUserJid())) { try { ChatMessage chatMessage = new ChatMessage(chatRoom.getChatJid(), fromJid.asEntityBareJidIfPossible(), XMPPHelper.asResourcepartString(fromJid.getResourceOrNull()), fromNick, ts, message); listener.onChatRoomMessageReceived(chatRoom, chatMessage); } catch(Throwable t) { logger.error("Listener error", t); } } */ } else { logger.warn("Empty message {}", message); } } @Override public void kicked(Jid actor, String reason) { } @Override public void voiceGranted() { } @Override public void voiceRevoked() { } @Override public void banned(Jid actor, String reason) { } @Override public void membershipGranted() { } @Override public void membershipRevoked() { } @Override public void moderatorGranted() { } @Override public void moderatorRevoked() { } @Override public void ownershipGranted() { } @Override public void ownershipRevoked() { } @Override public void adminGranted() { } @Override public void adminRevoked() { } @Override public void roomDestroyed(MultiUserChat alternateMuc, String reason) { /* if (isDisconnecting.get()) return; final EntityBareJid chatJid = getChatRoom().getChatJid(); logger.debug("Group chat destroyed [{}, {}]", chatJid.toString(), reason); try { synchronized(groupChats) { if (groupChats.containsKey(chatJid)) doRemoveGroupChat(false, chatJid); } } catch(XMPPException | SmackException | InterruptedException ex) { logger.error("UserStatusListener error", ex); } */ } @Override public void joined(EntityFullJid participant) { try { listener.onChatRoomParticipantJoined(chatRoom, participant); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void left(EntityFullJid participant) { try { listener.onChatRoomParticipantLeft(chatRoom, participant, false); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void kicked(EntityFullJid participant, Jid actor, String string) { try { listener.onChatRoomParticipantLeft(chatRoom, participant, true); } catch (Throwable t) { logger.error("Listener error", t); } } @Override public void voiceGranted(EntityFullJid efj) { } @Override public void voiceRevoked(EntityFullJid efj) { } @Override public void banned(EntityFullJid efj, Jid jid, String string) { } @Override public void membershipGranted(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void membershipRevoked(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void moderatorGranted(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void moderatorRevoked(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void ownershipGranted(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void ownershipRevoked(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void adminGranted(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void adminRevoked(EntityFullJid efj) { logger.debug("{}", efj.toString()); } @Override public void nicknameChanged(EntityFullJid efj, Resourcepart r) { logger.debug("{}", efj.toString()); } } public static boolean isGroupChat(EntityBareJid chatJid) { return isGroupChat(chatJid.toString()); } public static boolean isGroupChat(String chatEntityBareJid) { return !isInstantChat(chatEntityBareJid); } public static boolean isInstantChat(EntityBareJid chatJid) { return isInstantChat(chatJid.toString()); } public static boolean isInstantChat(String chatEntityBareJid) { return StringUtils.contains(chatEntityBareJid, "@" + CHAT_DOMAIN_PREFIX + "."); } private final class GuessMucAffiliateResult { public EntityBareJid jid; public String nick; public GuessMucAffiliateResult(EntityBareJid jid, String nick) { this.jid = jid; this.nick = nick; } } private final class GuessMucFromResult { public EntityFullJid jid; public String nick; public GuessMucFromResult(EntityFullJid jid, String nick) { this.jid = jid; this.nick = nick; } } /* private EntityFullJid guessOccupantJid(MultiUserChat muc, EntityFullJid messageFrom) { Occupant occupant = muc.getOccupant(messageFrom); return (occupant != null) ? occupant.getJid().asEntityFullJidIfPossible() : messageFrom; } private String guessOccupantNick(Roster roster, MultiUserChat muc, EntityFullJid messageFrom) { Occupant occupant = muc.getOccupant(messageFrom); if (occupant != null) { String nick = getRosterEntryNickname(roster, occupant.getJid().asBareJid(), false); if (!StringUtils.isBlank(nick)) return nick; nick = XMPPHelper.asResourcepartString(occupant.getNick()); if (!StringUtils.isBlank(nick)) return nick; return XMPPHelper.buildGuessedString(occupant.getJid().asBareJid()); } else { return XMPPHelper.asResourcepartString(messageFrom.getResourceOrNull()); } } private String guessOccupantNick(Roster roster, Occupant occupant) { String nick = getRosterEntryNickname(roster, occupant.getJid().asBareJid(), false); if (!StringUtils.isBlank(nick)) return nick; nick = XMPPHelper.asResourcepartString(occupant.getNick()); if (!StringUtils.isBlank(nick)) return nick; return XMPPHelper.buildGuessedString(occupant.getJid().asBareJid()); } public void login() throws XMPPClientException { checkConnection(); try { final Roster roster = getRoster(); roster.addRosterListener(rosterListener); final ChatManager chatMgr = getChatManager(); chatMgr.addIncomingListener(dcIncomingMessageListener); final MultiUserChatManager muChatMgr = getMUChatManager(); muChatMgr.addInvitationListener(mucInvitationListener); this.nickname = XMPPHelper.asResourcepart(nickname); // Restore previous direct (one-to-one) chat rooms if (history != null) { synchronized(directChats) { for(ChatRoom chat : history.getChats()) { if (chat instanceof DirectChatRoom) { directChats.put(chat.getChatJid(), new DChat((DirectChatRoom)chat)); } } } } con.login(); ensureRosterLoaded(roster); // Checks for updated nicks... synchronized(directChats) { for(DChat dchat : directChats.values()) { final EntityBareJid withJid = dchat.getChatRoom().getWithJid().asEntityBareJid(); String nick = dchat.getChatRoom().getName(); String friendNick = getRosterEntryNickname(roster, withJid, false); if (friendNick != null) nick = friendNick; if (StringUtils.isBlank(nick)) nick = XMPPHelper.buildGuessedNickname(withJid.toString()); if (!StringUtils.equals(dchat.getChatRoom().getName(), nick)) { // Nick updated } } } } catch(SmackException | XMPPException | InterruptedException | IOException ex) { throw new XMPPClientException(ex); } } */ /* private synchronized void ensureAuthentication() throws XMPPClientException { checkConnection(); if (!con.isAuthenticated()) throw new XMPPClientException("No logged user"); } */ }