From 547138b3258389be165bab95fb04fa3adcadb950 Mon Sep 17 00:00:00 2001 From: Miguel Hincapie Date: Thu, 6 Sep 2018 10:18:48 -0500 Subject: [PATCH] Improve ChatMarkersManager * Created filters to be used with incoming and outgoing message stanzas * Added a list of ChatMarkersListener and its add and remove methods. * Added a stanza listener for outgoing messages. * Added a stanza listener for incoming messages. * Added discover feature for XEP-0333. * Added methods to inform a message was: received, displayed or ack * Added javadoc autor tag. --- .../smack/filter/MessageTypeFilter.java | 1 + .../chat_markers/ChatMarkersListener.java | 38 ++++ .../chat_markers/ChatMarkersManager.java | 163 +++++++++++++++++- .../smackx/chat_markers/ChatMarkersState.java | 45 +++++ .../element/ChatMarkersElements.java | 9 +- .../filter/ChatMarkersFilter.java | 37 ++++ .../filter/EligibleForChatMarker.java | 74 ++++++++ .../chat_markers/filter/package-info.java | 24 +++ 8 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersListener.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersState.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/ChatMarkersFilter.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/EligibleForChatMarker.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/package-info.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java index 61d6ed276..a73678dce 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java @@ -35,6 +35,7 @@ public final class MessageTypeFilter extends FlexibleStanzaTypeFilter { public static final StanzaFilter HEADLINE = new MessageTypeFilter(Type.headline); public static final StanzaFilter ERROR = new MessageTypeFilter(Type.error); public static final StanzaFilter NORMAL_OR_CHAT = new OrFilter(NORMAL, CHAT); + public static final StanzaFilter NORMAL_OR_CHAT_OR_GROUPCHAT = new OrFilter(NORMAL_OR_CHAT, GROUPCHAT); public static final StanzaFilter NORMAL_OR_CHAT_OR_HEADLINE = new OrFilter(NORMAL_OR_CHAT, HEADLINE); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersListener.java new file mode 100644 index 000000000..537b59187 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersListener.java @@ -0,0 +1,38 @@ +/** + * + * Copyright 2018 Miguel Hincapie. + * + * 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.smackx.chat_markers; + +import org.jivesoftware.smack.chat2.Chat; +import org.jivesoftware.smack.packet.Message; + +/** + * Chat Markers Manager class (XEP-0333). + * + * @author Miguel Hincapie + * @see XEP-0333: Chat + * Markers + */ +public interface ChatMarkersListener { + /** + * Called in ChatMarkersManager when a new message with a markable tag arrives. + * + * @param chatMarkersState the current state of the message. + * @param message the new incoming message with a markable XML tag. + * @param chat associated to the message. This element can be NULL. + */ + void newChatMarkerMessage(ChatMarkersState chatMarkersState, Message message, Chat chat); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersManager.java index a7c03798f..0e2c2a7a9 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersManager.java @@ -17,24 +17,45 @@ package org.jivesoftware.smackx.chat_markers; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException.XMPPErrorException; - +import org.jivesoftware.smack.chat2.Chat; +import org.jivesoftware.smack.chat2.ChatManager; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.MessageWithBodiesFilter; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.filter.PossibleFromTypeFilter; +import org.jivesoftware.smack.filter.StanzaExtensionFilter; +import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements; +import org.jivesoftware.smackx.chat_markers.filter.ChatMarkersFilter; +import org.jivesoftware.smackx.chat_markers.filter.EligibleForChatMarker; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.EntityFullJid; + /** * Chat Markers Manager class (XEP-0333). * * @see XEP-0333: Chat * Markers + * @author Miguel Hincapie * @author Fernando Ramirez * */ @@ -51,10 +72,30 @@ public final class ChatMarkersManager extends Manager { private static final Map INSTANCES = new WeakHashMap<>(); + // @FORMATTER:OFF + private static final StanzaFilter INCOMING_MESSAGE_FILTER = new AndFilter( + MessageTypeFilter.NORMAL_OR_CHAT_OR_GROUPCHAT, + new StanzaExtensionFilter(ChatMarkersElements.NAMESPACE), + PossibleFromTypeFilter.ENTITY_BARE_JID, + EligibleForChatMarker.INSTANCE + ); + + private static final StanzaFilter OUTGOING_MESSAGE_FILTER = new AndFilter( + MessageTypeFilter.NORMAL_OR_CHAT_OR_GROUPCHAT, + MessageWithBodiesFilter.INSTANCE, + new NotFilter(ChatMarkersFilter.INSTANCE), + EligibleForChatMarker.INSTANCE + ); + // @FORMATTER:ON + + private final Set incomingListeners = new CopyOnWriteArraySet<>(); + + private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); + /** * Get the singleton instance of ChatMarkersManager. * - * @param connection + * @param connection the connection used to get the ChatMarkersManager instance. * @return the instance of ChatMarkersManager */ public static synchronized ChatMarkersManager getInstanceFor(XMPPConnection connection) { @@ -70,16 +111,66 @@ public final class ChatMarkersManager extends Manager { private ChatMarkersManager(XMPPConnection connection) { super(connection); + connection.addStanzaInterceptor(new StanzaListener() { + @Override + public void processStanza(Stanza packet) + throws + NotConnectedException, + InterruptedException, + SmackException.NotLoggedInException { + Message message = (Message) packet; + // add a markable extension + message.addExtension(new ChatMarkersElements.MarkableExtension()); + } + }, OUTGOING_MESSAGE_FILTER); + + connection.addSyncStanzaListener(new StanzaListener() { + @Override + public void processStanza(Stanza packet) + throws + NotConnectedException, + InterruptedException, + SmackException.NotLoggedInException { + final Message message = (Message) packet; + + EntityFullJid fullFrom = message.getFrom().asEntityFullJidIfPossible(); + EntityBareJid bareFrom = fullFrom.asEntityBareJid(); + final Chat chat = ChatManager.getInstanceFor(connection()).chatWith(bareFrom); + + asyncButOrdered.performAsyncButOrdered(chat, new Runnable() { + @Override + public void run() { + for (ChatMarkersListener listener : incomingListeners) { + if (ChatMarkersElements.MarkableExtension.from(message) != null) { + listener.newChatMarkerMessage(ChatMarkersState.markable, message, chat); + } + else if (ChatMarkersElements.ReceivedExtension.from(message) != null) { + listener.newChatMarkerMessage(ChatMarkersState.received, message, chat); + } + else if (ChatMarkersElements.DisplayedExtension.from(message) != null) { + listener.newChatMarkerMessage(ChatMarkersState.displayed, message, chat); + } + else if (ChatMarkersElements.AcknowledgedExtension.from(message) != null) { + listener.newChatMarkerMessage(ChatMarkersState.acknowledged, message, chat); + } + } + } + }); + + } + }, INCOMING_MESSAGE_FILTER); + + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(ChatMarkersElements.NAMESPACE); } /** * Returns true if Chat Markers is supported by the server. * * @return true if Chat Markers is supported by the server. - * @throws NotConnectedException - * @throws XMPPErrorException - * @throws NoResponseException - * @throws InterruptedException + * @throws NotConnectedException if the connection is not connected. + * @throws XMPPErrorException in case an error response was received. + * @throws NoResponseException if no response was received. + * @throws InterruptedException if the connection is interrupted. */ public boolean isSupportedByServer() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { @@ -87,4 +178,64 @@ public final class ChatMarkersManager extends Manager { .serverSupportsFeature(ChatMarkersElements.NAMESPACE); } + /** + * Register a ChatMarkersListener. That listener will be informed about new + * incoming markable messages. + * + * @param listener ChatMarkersListener + * @return true, if the listener was not registered before + */ + public boolean addIncomingChatMarkerMessageListener(ChatMarkersListener listener) { + return incomingListeners.add(listener); + } + + /** + * Unregister a ChatMarkersListener. + * + * @param listener ChatMarkersListener + * @return true, if the listener was registered before + */ + public boolean removeIncomingChatMarkerMessageListener(ChatMarkersListener listener) { + return incomingListeners.remove(listener); + } + + public void markMessageAsReceived(Message message) + throws + NotConnectedException, + InterruptedException, + IllegalArgumentException { + if (message == null) { + throw new IllegalArgumentException("To and From needed"); + } + message.addExtension(new ChatMarkersElements.ReceivedExtension(message.getStanzaId())); + sendChatMarkerMessage(message); + } + + public void markMessageAsDisplayed(Message message) + throws + NotConnectedException, + InterruptedException, + IllegalArgumentException { + if (message == null) { + throw new IllegalArgumentException("To and From needed"); + } + message.addExtension(new ChatMarkersElements.DisplayedExtension(message.getStanzaId())); + sendChatMarkerMessage(message); + } + + public void markMessageAsAcknowledged(Message message) + throws + NotConnectedException, + InterruptedException, + IllegalArgumentException { + if (message == null) { + throw new IllegalArgumentException("To and From needed"); + } + message.addExtension(new ChatMarkersElements.AcknowledgedExtension(message.getStanzaId())); + sendChatMarkerMessage(message); + } + + private void sendChatMarkerMessage(Message message) throws NotConnectedException, InterruptedException { + connection().sendStanza(message); + } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersState.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersState.java new file mode 100644 index 000000000..dfc1bd27a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/ChatMarkersState.java @@ -0,0 +1,45 @@ +/** + * + * Copyright © 2018 Miguel Hincapie + * + * 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.smackx.chat_markers; + +/** + * Chat Markers elements (XEP-0333). + * + * @author Miguel Hincapie + * @see XEP-0333: Chat + * Markers + */ +public enum ChatMarkersState { + /** + * Indicates that a message can be marked with a Chat Marker and is therefore + * a "markable message". + */ + markable, + /** + * The message has been received by a client. + */ + received, + /** + * The message has been displayed to a user in a active chat and not in a system notification. + */ + displayed, + /** + * The message has been acknowledged by some user interaction e.g. pressing an + * acknowledgement button. + */ + acknowledged +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/element/ChatMarkersElements.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/element/ChatMarkersElements.java index d058e55d1..53b2b8552 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/element/ChatMarkersElements.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/element/ChatMarkersElements.java @@ -19,6 +19,7 @@ package org.jivesoftware.smackx.chat_markers.element; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.chat_markers.ChatMarkersState; /** * Chat Markers elements (XEP-0333). @@ -45,7 +46,7 @@ public class ChatMarkersElements { /** * markable element. */ - public static final String ELEMENT = "markable"; + public static final String ELEMENT = ChatMarkersState.markable.toString(); public MarkableExtension() { } @@ -85,7 +86,7 @@ public class ChatMarkersElements { /** * received element. */ - public static final String ELEMENT = "received"; + public static final String ELEMENT = ChatMarkersState.received.toString(); private final String id; @@ -138,7 +139,7 @@ public class ChatMarkersElements { /** * displayed element. */ - public static final String ELEMENT = "displayed"; + public static final String ELEMENT = ChatMarkersState.displayed.toString(); private final String id; @@ -191,7 +192,7 @@ public class ChatMarkersElements { /** * acknowledged element. */ - public static final String ELEMENT = "acknowledged"; + public static final String ELEMENT = ChatMarkersState.acknowledged.toString(); private final String id; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/ChatMarkersFilter.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/ChatMarkersFilter.java new file mode 100644 index 000000000..0571e7109 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/ChatMarkersFilter.java @@ -0,0 +1,37 @@ +/** + * + * Copyright 2018 Miguel Hincapie + * + * 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.smackx.chat_markers.filter; + +import org.jivesoftware.smack.filter.StanzaExtensionFilter; +import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements; + +/** + * Chat Markers Manager class (XEP-0333). + * + * @author HINCM008 6/08/2018. + * @see XEP-0333: Chat + * Markers + */ +public final class ChatMarkersFilter extends StanzaExtensionFilter { + + public static final StanzaFilter INSTANCE = new ChatMarkersFilter(ChatMarkersElements.NAMESPACE); + + private ChatMarkersFilter(String namespace) { + super(namespace); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/EligibleForChatMarker.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/EligibleForChatMarker.java new file mode 100644 index 000000000..cdce44625 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/EligibleForChatMarker.java @@ -0,0 +1,74 @@ +/** + * + * Copyright 2018 Miguel Hincapie. + * + * 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.smackx.chat_markers.filter; + +import org.jivesoftware.smack.filter.StanzaExtensionFilter; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.chatstates.ChatState; +import org.jivesoftware.smackx.chatstates.ChatStateManager; + +/** + * Chat Markers Manager class (XEP-0333). + * + * @author Miguel Hincapie + * @see XEP-0333: Chat + * Markers + */ +public final class EligibleForChatMarker extends StanzaExtensionFilter { + + public static final EligibleForChatMarker INSTANCE = new EligibleForChatMarker(ChatStateManager.NAMESPACE); + + private EligibleForChatMarker(String namespace) { + super(namespace); + } + + /** + * From XEP-0333, Protocol Format: The Chat Marker MUST have an 'id' which is the 'id' of the + * message being marked.
+ * In order to make Chat Markers works together with XEP-0085 as it said in + * 8.5 Interaction with Chat States, only messages with active chat + * state are accepted. + * + * @param message to be analyzed. + * @return true if the message contains a stanza Id. + * @see XEP-0333: Chat Markers + */ + @Override + public boolean accept(Stanza message) { + if (StringUtils.isNullOrEmpty(message.getStanzaId())) { + return false; + } + + if (super.accept(message)) { + ExtensionElement extension = message.getExtension(ChatStateManager.NAMESPACE); + String chatStateElementName = extension.getElementName(); + + ChatState state; + try { + state = ChatState.valueOf(chatStateElementName); + return (state == ChatState.active); + } + catch (Exception ex) { + return false; + } + } + + return true; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/package-info.java new file mode 100644 index 000000000..52baf52cf --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/chat_markers/filter/package-info.java @@ -0,0 +1,24 @@ +/** + * + * Copyright 2018 Miguel Hincapie + * + * 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. + */ +/** + * Chat Markers elements (XEP-0333). + * + * @see XEP-0333: Chat + * Markers + * + */ +package org.jivesoftware.smackx.chat_markers.filter;