All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.smack.chat2.ChatManager Maven / Gradle / Ivy

/**
 *
 * Copyright 2017-2019 Florian Schmaus.
 *
 * 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.smack.chat2;

import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromTypeFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.MessageWithBodiesFilter;
import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.ToTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageView;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.roster.AbstractRosterListener;
import org.jivesoftware.smack.roster.Roster;

import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension;

import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;

/**
 * A chat manager for 1:1 XMPP instant messaging chats.
 * 

* This manager and the according {@link Chat} API implement "Resource Locking" (XEP-0296). Support for Carbon Copies * (XEP-0280) will be added once the XEP has progressed from experimental. *

* * @see XEP-0296: Best Practices for Resource Locking */ public final class ChatManager extends Manager { private static final Map INSTANCES = new WeakHashMap<>(); public static synchronized ChatManager getInstanceFor(XMPPConnection connection) { ChatManager chatManager = INSTANCES.get(connection); if (chatManager == null) { chatManager = new ChatManager(connection); INSTANCES.put(connection, chatManager); } return chatManager; } // @FORMATTER:OFF private static final StanzaFilter MESSAGE_FILTER = new AndFilter( MessageTypeFilter.NORMAL_OR_CHAT, new OrFilter(MessageWithBodiesFilter.INSTANCE, new StanzaExtensionFilter(XHTMLExtension.ELEMENT, XHTMLExtension.NAMESPACE)) ); private static final StanzaFilter OUTGOING_MESSAGE_FILTER = new AndFilter( MESSAGE_FILTER, ToTypeFilter.ENTITY_FULL_OR_BARE_JID ); private static final StanzaFilter INCOMING_MESSAGE_FILTER = new AndFilter( MESSAGE_FILTER, FromTypeFilter.ENTITY_FULL_JID ); // @FORMATTER:ON private final Map chats = new ConcurrentHashMap<>(); private final Set incomingListeners = new CopyOnWriteArraySet<>(); private final Set outgoingListeners = new CopyOnWriteArraySet<>(); private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); private boolean xhtmlIm; private ChatManager(final XMPPConnection connection) { super(connection); connection.addSyncStanzaListener(new StanzaListener() { @Override public void processStanza(Stanza stanza) { final Message message = (Message) stanza; if (!shouldAcceptMessage(message)) { return; } final Jid from = message.getFrom(); final EntityFullJid fullFrom = from.asEntityFullJidOrThrow(); final EntityBareJid bareFrom = fullFrom.asEntityBareJid(); final Chat chat = chatWith(bareFrom); chat.lockedResource = fullFrom; asyncButOrdered.performAsyncButOrdered(chat, new Runnable() { @Override public void run() { for (IncomingChatMessageListener listener : incomingListeners) { listener.newIncomingMessage(bareFrom, message, chat); } } }); } }, INCOMING_MESSAGE_FILTER); connection.addMessageInterceptor(messageBuilder -> { if (!shouldAcceptMessage(messageBuilder)) { return; } final EntityBareJid to = messageBuilder.getTo().asEntityBareJidOrThrow(); final Chat chat = chatWith(to); for (OutgoingChatMessageListener listener : outgoingListeners) { listener.newOutgoingMessage(to, messageBuilder, chat); } }, m -> { return OUTGOING_MESSAGE_FILTER.accept(m); }); Roster roster = Roster.getInstanceFor(connection); roster.addRosterListener(new AbstractRosterListener() { @Override public void presenceChanged(Presence presence) { final Jid from = presence.getFrom(); final EntityBareJid bareFrom = from.asEntityBareJidIfPossible(); if (bareFrom == null) { return; } final Chat chat = chats.get(bareFrom); if (chat == null) { return; } if (chat.lockedResource == null) { // According to XEP-0296, no action is required for resource locking upon receiving a presence if no // resource is currently locked. return; } final EntityFullJid fullFrom = from.asEntityFullJidIfPossible(); if (chat.lockedResource.equals(fullFrom)) { return; } if (chat.lastPresenceOfLockedResource == null) { // We have no last known presence from the locked resource. chat.lastPresenceOfLockedResource = presence; return; } if (chat.lastPresenceOfLockedResource.getMode() != presence.getMode() || chat.lastPresenceOfLockedResource.getType() != presence.getType()) { chat.unlockResource(); } } }); } private boolean shouldAcceptMessage(MessageView message) { if (message.hasExtension(Message.Body.QNAME)) { return true; } // Message has no XMPP-IM bodies, abort here if xhtmlIm is not enabled. if (!xhtmlIm) { return false; } XHTMLExtension xhtmlExtension = XHTMLExtension.from(message); if (xhtmlExtension == null) { // Message has no XHTML-IM extension, abort. return false; } return true; } /** * Add a new listener for incoming chat messages. * * @param listener the listener to add. * @return true if the listener was not already added. */ public boolean addIncomingListener(IncomingChatMessageListener listener) { return incomingListeners.add(listener); } /** * Remove an incoming chat message listener. * * @param listener the listener to remove. * @return true if the listener was active and got removed. */ public boolean removeIncomingListener(IncomingChatMessageListener listener) { return incomingListeners.remove(listener); } /** * Add a new listener for outgoing chat messages. * * @param listener the listener to add. * @return true if the listener was not already added. */ public boolean addOutgoingListener(OutgoingChatMessageListener listener) { return outgoingListeners.add(listener); } /** * Remove an outgoing chat message listener. * * @param listener the listener to remove. * @return true if the listener was active and got removed. */ public boolean removeOutgoingListener(OutgoingChatMessageListener listener) { return outgoingListeners.remove(listener); } /** * Start a new or retrieve the existing chat with jid. * * @param jid the XMPP address of the other entity to chat with. * @return the Chat API for the given XMPP address. */ public Chat chatWith(EntityBareJid jid) { Chat chat = chats.get(jid); if (chat == null) { synchronized (chats) { // Double-checked locking. chat = chats.get(jid); if (chat != null) { return chat; } chat = new Chat(connection(), jid); chats.put(jid, chat); } } return chat; } /** * Also notify about messages containing XHTML-IM. * * @param xhtmlIm TODO javadoc me please */ public void setXhmtlImEnabled(boolean xhtmlIm) { this.xhtmlIm = xhtmlIm; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy