Introduce XMPPConnection.add(Message|Presence)Interceptor

add deprecate addStanzaInterceptor().
This commit is contained in:
Florian Schmaus 2019-10-25 13:57:18 +02:00
parent 5db6191110
commit e2d206e741
24 changed files with 419 additions and 102 deletions

View File

@ -100,8 +100,12 @@ import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.MessageOrPresence;
import org.jivesoftware.smack.packet.MessageOrPresenceBuilder;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.PresenceBuilder;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
@ -123,11 +127,13 @@ import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
@ -243,6 +249,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<StanzaListener, InterceptorWrapper> interceptors =
new HashMap<>();
private final Map<Consumer<MessageBuilder>, GenericInterceptorWrapper<MessageBuilder, Message>> messageInterceptors = new HashMap<>();
private final Map<Consumer<PresenceBuilder>, GenericInterceptorWrapper<PresenceBuilder, Presence>> presenceInterceptors = new HashMap<>();
private XmlEnvironment incomingStreamXmlEnvironment;
protected XmlEnvironment outgoingStreamXmlEnvironment;
@ -849,8 +859,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
// the content of the stanza.
firePacketInterceptors(stanza);
sendStanzaInternal(stanza);
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
sendStanzaInternal(stanzaAfterInterceptors);
}
/**
@ -1204,6 +1214,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
});
}
@Deprecated
@Override
public void addStanzaInterceptor(StanzaListener packetInterceptor,
StanzaFilter packetFilter) {
@ -1216,6 +1227,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
@Deprecated
@Override
public void removeStanzaInterceptor(StanzaListener packetInterceptor) {
synchronized (interceptors) {
@ -1223,15 +1235,83 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> void addInterceptor(
Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors, Consumer<MPB> interceptor,
Predicate<MP> filter) {
Objects.requireNonNull(interceptor, "Interceptor must not be null");
GenericInterceptorWrapper<MPB, MP> interceptorWrapper = new GenericInterceptorWrapper<>(interceptor, filter);
synchronized (interceptors) {
interceptors.put(interceptor, interceptorWrapper);
}
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> void removeInterceptor(
Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors, Consumer<MPB> interceptor) {
synchronized (interceptors) {
interceptors.remove(interceptor);
}
}
@Override
public void addMessageInterceptor(Consumer<MessageBuilder> messageInterceptor, Predicate<Message> messageFilter) {
addInterceptor(messageInterceptors, messageInterceptor, messageFilter);
}
@Override
public void removeMessageInterceptor(Consumer<MessageBuilder> messageInterceptor) {
removeInterceptor(messageInterceptors, messageInterceptor);
}
@Override
public void addPresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor,
Predicate<Presence> presenceFilter) {
addInterceptor(presenceInterceptors, presenceInterceptor, presenceFilter);
}
@Override
public void removePresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor) {
removeInterceptor(presenceInterceptors, presenceInterceptor);
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> MP fireMessageOrPresenceInterceptors(
MP messageOrPresence, Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors) {
List<Consumer<MPB>> interceptorsToInvoke = new LinkedList<>();
synchronized (interceptors) {
for (GenericInterceptorWrapper<MPB, MP> interceptorWrapper : interceptors.values()) {
if (interceptorWrapper.filterMatches(messageOrPresence)) {
Consumer<MPB> interceptor = interceptorWrapper.getInterceptor();
interceptorsToInvoke.add(interceptor);
}
}
}
// Avoid transforming the stanza to a builder if there is no interceptor.
if (interceptorsToInvoke.isEmpty()) {
return messageOrPresence;
}
MPB builder = messageOrPresence.asBuilder();
for (Consumer<MPB> interceptor : interceptorsToInvoke) {
interceptor.accept(builder);
}
// Now that the interceptors have (probably) modified the stanza in its builder form, we need to re-assemble it.
messageOrPresence = builder.build();
return messageOrPresence;
}
/**
* Process interceptors. Interceptors may modify the stanza that is about to be sent.
* Since the thread that requested to send the stanza will invoke all interceptors, it
* is important that interceptors perform their work as soon as possible so that the
* thread does not remain blocked for a long period.
*
* @param packet the stanza that is going to be sent to the server
* @param packet the stanza that is going to be sent to the server.
* @return the, potentially modified stanza, after the interceptors are run.
*/
private void firePacketInterceptors(Stanza packet) {
private Stanza firePacketInterceptors(Stanza packet) {
List<StanzaListener> interceptorsToInvoke = new LinkedList<>();
synchronized (interceptors) {
for (InterceptorWrapper interceptorWrapper : interceptors.values()) {
@ -1247,6 +1327,22 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
LOGGER.log(Level.SEVERE, "Packet interceptor threw exception", e);
}
}
final Stanza stanzaAfterInterceptors;
if (packet instanceof Message) {
Message message = (Message) packet;
stanzaAfterInterceptors = fireMessageOrPresenceInterceptors(message, messageInterceptors);
}
else if (packet instanceof Presence) {
Presence presence = (Presence) packet;
stanzaAfterInterceptors = fireMessageOrPresenceInterceptors(presence, presenceInterceptors);
} else {
// We do not (yet) support interceptors for IQ stanzas.
assert packet instanceof IQ;
stanzaAfterInterceptors = packet;
}
return stanzaAfterInterceptors;
}
/**
@ -1674,6 +1770,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* A wrapper class to associate a stanza filter with an interceptor.
*/
@Deprecated
// TODO: Remove once addStanzaInterceptor is gone.
protected static class InterceptorWrapper {
private final StanzaListener packetInterceptor;
@ -1699,6 +1797,24 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
private static final class GenericInterceptorWrapper<MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> {
private final Consumer<MPB> stanzaInterceptor;
private final Predicate<MP> stanzaFilter;
private GenericInterceptorWrapper(Consumer<MPB> stanzaInterceptor, Predicate<MP> stanzaFilter) {
this.stanzaInterceptor = stanzaInterceptor;
this.stanzaFilter = stanzaFilter;
}
private boolean filterMatches(MP stanza) {
return stanzaFilter == null || stanzaFilter.test(stanza);
}
public Consumer<MPB> getInterceptor() {
return stanzaInterceptor;
}
}
@Override
public int getConnectionCounter() {
return connectionCounterValue;

View File

@ -27,9 +27,15 @@ import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.PresenceBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaFactory;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.Predicate;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityFullJid;
@ -361,7 +367,7 @@ public interface XMPPConnection {
boolean removeStanzaListener(StanzaListener stanzaListener);
/**
* Registers a <b>synchronous</b> stanza listener with this connection. A stanza listener will be invoked only when
* Registers a <b>synchronous</b> stanza listener with this connection. A stanza listener will be invoked only when
* an incoming stanza is received. A stanza filter determines which stanzas will be delivered to the listener. If
* the same stanza listener is added again with a different filter, only the new filter will be used.
* <p>
@ -446,16 +452,65 @@ public interface XMPPConnection {
*
* @param stanzaInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param stanzaFilter the stanza filter to use.
* @deprecated use {@link #addMessageInterceptor(Consumer, Predicate)} or {@link #addPresenceInterceptor(Consumer, Predicate)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
void addStanzaInterceptor(StanzaListener stanzaInterceptor, StanzaFilter stanzaFilter);
/**
* Removes a stanza interceptor.
*
* @param stanzaInterceptor the stanza interceptor to remove.
* @deprecated use {@link #removeMessageInterceptor(Consumer)} or {@link #removePresenceInterceptor(Consumer)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
void removeStanzaInterceptor(StanzaListener stanzaInterceptor);
/**
* Registers a stanza interceptor with this connection. The interceptor will be
* invoked every time a stanza is about to be sent by this connection. Interceptors
* may modify the stanza to be sent. A stanza filter determines which stanzas
* will be delivered to the interceptor.
*
* <p>
* NOTE: For a similar functionality on incoming stanzas, see {@link #addAsyncStanzaListener(StanzaListener, StanzaFilter)}.
* </p>
*
* @param messageInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param messageFilter the stanza filter to use.
*/
void addMessageInterceptor(Consumer<MessageBuilder> messageInterceptor, Predicate<Message> messageFilter);
/**
* Removes a message interceptor.
*
* @param messageInterceptor the message interceptor to remove.
*/
void removeMessageInterceptor(Consumer<MessageBuilder> messageInterceptor);
/**
* Registers a stanza interceptor with this connection. The interceptor will be
* invoked every time a stanza is about to be sent by this connection. Interceptors
* may modify the stanza to be sent. A stanza filter determines which stanzas
* will be delivered to the interceptor.
*
* <p>
* NOTE: For a similar functionality on incoming stanzas, see {@link #addAsyncStanzaListener(StanzaListener, StanzaFilter)}.
* </p>
*
* @param presenceInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param presenceFilter the stanza filter to use.
*/
void addPresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor, Predicate<Presence> presenceFilter);
/**
* Removes a presence interceptor.
*
* @param presenceInterceptor the stanza interceptor to remove.
*/
void removePresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor);
/**
* Returns the current value of the reply timeout in milliseconds for request for this
* XMPPConnection instance.

View File

@ -18,6 +18,7 @@
package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Predicate;
/**
* Defines a way to filter stanzas for particular attributes. Stanza filters are used when
@ -51,7 +52,7 @@ import org.jivesoftware.smack.packet.Stanza;
* @see org.jivesoftware.smack.StanzaListener
* @author Matt Tucker
*/
public interface StanzaFilter {
public interface StanzaFilter extends Predicate<Stanza> {
/**
* Tests whether or not the specified stanza should pass the filter.
@ -60,4 +61,9 @@ public interface StanzaFilter {
* @return true if and only if <code>stanza</code> passes the filter.
*/
boolean accept(Stanza stanza);
@Override
default boolean test(Stanza stanza) {
return accept(stanza);
}
}

View File

@ -60,7 +60,8 @@ import org.jxmpp.stringprep.XmppStringprepException;
*
* @author Matt Tucker
*/
public final class Message extends Stanza implements MessageView, TypedCloneable<Message> {
public final class Message extends MessageOrPresence<MessageBuilder>
implements MessageView, TypedCloneable<Message> {
public static final String ELEMENT = "message";
public static final String BODY = "body";
@ -534,6 +535,7 @@ public final class Message extends Stanza implements MessageView, TypedCloneable
return ELEMENT;
}
@Override
public MessageBuilder asBuilder() {
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
}
@ -664,6 +666,7 @@ public final class Message extends Stanza implements MessageView, TypedCloneable
public static final String ELEMENT = "body";
public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
enum BodyElementNamespace {
client(StreamOpen.CLIENT_NAMESPACE),

View File

@ -22,7 +22,7 @@ import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.ToStringUtil;
public final class MessageBuilder extends StanzaBuilder<MessageBuilder> implements MessageView {
public final class MessageBuilder extends MessageOrPresenceBuilder<Message, MessageBuilder> implements MessageView {
static final MessageBuilder EMPTY = new MessageBuilder(() -> {
return null;
});
@ -153,6 +153,7 @@ public final class MessageBuilder extends StanzaBuilder<MessageBuilder> implemen
return this;
}
@Override
public Message build() {
return new Message(this);
}

View File

@ -0,0 +1,36 @@
/**
*
* Copyright 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.packet;
public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?, ?>> extends Stanza {
@Deprecated
// TODO: Remove in Smack 4.5.
protected MessageOrPresence() {
}
protected MessageOrPresence(StanzaBuilder<?> stanzaBuilder) {
super(stanzaBuilder);
}
protected MessageOrPresence(Stanza other) {
super(other);
}
public abstract MPB asBuilder();
}

View File

@ -0,0 +1,42 @@
/**
*
* Copyright 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.packet;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
public abstract class MessageOrPresenceBuilder<MP extends MessageOrPresence<? extends MessageOrPresenceBuilder<MP, SB>>, SB extends StanzaBuilder<SB>>
extends StanzaBuilder<SB> {
protected MessageOrPresenceBuilder(Stanza stanza, StanzaIdSource stanzaIdSource) {
super(stanza, stanzaIdSource);
}
protected MessageOrPresenceBuilder(Stanza stanza, String stanzaId) {
super(stanza, stanzaId);
}
protected MessageOrPresenceBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
protected MessageOrPresenceBuilder(String stanzaId) {
super(stanzaId);
}
public abstract MP build();
}

View File

@ -61,7 +61,8 @@ import org.jxmpp.jid.Jid;
*
* @author Matt Tucker
*/
public final class Presence extends Stanza implements PresenceView, TypedCloneable<Presence> {
public final class Presence extends MessageOrPresence<PresenceBuilder>
implements PresenceView, TypedCloneable<Presence> {
public static final String ELEMENT = "presence";
@ -277,6 +278,7 @@ public final class Presence extends Stanza implements PresenceView, TypedCloneab
return ELEMENT;
}
@Override
public PresenceBuilder asBuilder() {
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
}

View File

@ -21,7 +21,7 @@ import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ToStringUtil;
public final class PresenceBuilder extends StanzaBuilder<PresenceBuilder> implements PresenceView {
public final class PresenceBuilder extends MessageOrPresenceBuilder<Presence, PresenceBuilder> implements PresenceView {
static final PresenceBuilder EMPTY = new PresenceBuilder(() -> {
return null;
});
@ -102,6 +102,7 @@ public final class PresenceBuilder extends StanzaBuilder<PresenceBuilder> implem
return this;
}
@Override
public Presence build() {
return new Presence(this);
}

View File

@ -456,12 +456,8 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement {
}
}
/**
* Check if a stanza extension with the given namespace exists.
*
* @param namespace TODO javadoc me please
* @return true if a stanza extension exists, false otherwise.
*/
// Overridden in order to avoid an extra copy.
@Override
public boolean hasExtension(String namespace) {
synchronized (extensionElements) {
for (ExtensionElement packetExtension : extensionElements.values()) {

View File

@ -63,6 +63,26 @@ public interface StanzaView extends XmlLangElement {
<E extends ExtensionElement> E getExtension(QName qname);
default boolean hasExtension(QName qname) {
return getExtension(qname) != null;
}
/**
* Check if a extension element with the given namespace exists.
*
* @param namespace the namespace of the extension element to check for.
* @return true if a stanza extension exists, false otherwise.
*/
default boolean hasExtension(String namespace) {
for (ExtensionElement packetExtension : getExtensions()) {
if (packetExtension.getNamespace().equals(namespace)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
default <E extends ExtensionElement> E getExtension(Class<E> extensionElementClass) {
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);

View File

@ -0,0 +1,24 @@
/**
*
* Copyright 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.util;
// TODO: Replace with java.util.function.Predicate once Smack's minimum Android SDK level is 24 or higher.
public interface Predicate<T> {
boolean test(T t);
}

View File

@ -120,18 +120,10 @@ public final class ChatMarkersManager extends Manager {
chatManager = ChatManager.getInstanceFor(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(ChatMarkersElements.MarkableExtension.INSTANCE);
}
}, OUTGOING_MESSAGE_FILTER);
connection.addMessageInterceptor(mb -> mb.addExtension(ChatMarkersElements.MarkableExtension.INSTANCE),
m -> {
return OUTGOING_MESSAGE_FILTER.accept(m);
});
connection.addSyncStanzaListener(new StanzaListener() {
@Override

View File

@ -21,7 +21,6 @@ import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.filter.AndFilter;
@ -31,8 +30,9 @@ 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.Stanza;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
@ -62,12 +62,12 @@ public final class StableUniqueStanzaIdManager extends Manager {
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
private static final StanzaListener ADD_ORIGIN_ID_INTERCEPTOR = new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
Message message = (Message) stanza;
OriginIdElement.addOriginId(message);
}
private static final Consumer<MessageBuilder> ADD_ORIGIN_ID_INTERCEPTOR = mb -> OriginIdElement.addOriginId(mb);
// We need a filter for outgoing messages that do not carry an origin-id already.
private static final StanzaFilter ADD_ORIGIN_ID_FILTER = new AndFilter(OUTGOING_FILTER, new NotFilter(ORIGIN_ID_FILTER));
private static final Predicate<Message> ADD_ORIGIN_ID_PREDICATE = m -> {
return ADD_ORIGIN_ID_FILTER.accept(m);
};
static {
@ -112,10 +112,8 @@ public final class StableUniqueStanzaIdManager extends Manager {
* Start appending origin-id elements to outgoing stanzas and add the feature to disco.
*/
public synchronized void enable() {
connection().addMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, ADD_ORIGIN_ID_PREDICATE);
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
// We need a filter for outgoing messages that do not carry an origin-id already
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(ORIGIN_ID_FILTER));
connection().addStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, filter);
}
/**
@ -123,7 +121,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
*/
public synchronized void disable() {
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
connection().removeStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
connection().removeMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
}
/**

View File

@ -17,6 +17,7 @@
package org.jivesoftware.smackx.sid.element;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
@ -38,7 +39,10 @@ public class OriginIdElement extends StableAndUniqueIdElement {
*
* @param message message.
* @return the added origin-id element.
* @deprecated use {@link #addOriginId(MessageBuilder)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public static OriginIdElement addOriginId(Message message) {
OriginIdElement originId = new OriginIdElement();
message.addExtension(originId);
@ -47,6 +51,20 @@ public class OriginIdElement extends StableAndUniqueIdElement {
return originId;
}
/**
* Add an origin-id element to a message and set the stanzas id to the same id as in the origin-id element.
*
* @param messageBuilder the message builder to add an origin ID to.
* @return the added origin-id element.
*/
public static OriginIdElement addOriginId(MessageBuilder messageBuilder) {
OriginIdElement originId = new OriginIdElement();
messageBuilder.addExtension(originId);
// TODO: Find solution to have both the originIds stanzaId and a nice to look at incremental stanzaID.
// message.setStanzaId(originId.getId());
return originId;
}
/**
* Return true, if the message contains a origin-id element.
*

View File

@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils;
@ -71,12 +72,15 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
@Test
public void fromMessageTest() {
Message message = StanzaBuilder.buildMessage().build();
MessageBuilder messageBuilder = StanzaBuilder.buildMessage();
Message message = messageBuilder.build();
assertFalse(OriginIdElement.hasOriginId(message));
assertFalse(StanzaIdElement.hasStanzaId(message));
OriginIdElement.addOriginId(message);
OriginIdElement.addOriginId(messageBuilder);
message = messageBuilder.build();
assertTrue(OriginIdElement.hasOriginId(message));
StanzaIdElement stanzaId = new StanzaIdElement("alice@wonderland.lit");

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2018 Florian Schmaus.
* 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.
@ -24,7 +24,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.AndFilter;
@ -36,6 +35,7 @@ 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;
@ -124,22 +124,20 @@ public final class ChatManager extends Manager {
}
}, INCOMING_MESSAGE_FILTER);
connection.addStanzaInterceptor(new StanzaListener() {
@Override
public void processStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
Message message = (Message) stanza;
if (!shouldAcceptMessage(message)) {
return;
}
final EntityBareJid to = message.getTo().asEntityBareJidOrThrow();
final Chat chat = chatWith(to);
for (OutgoingChatMessageListener listener : outgoingListeners) {
listener.newOutgoingMessage(to, message, chat);
}
connection.addMessageInterceptor(messageBuilder -> {
if (!shouldAcceptMessage(messageBuilder)) {
return;
}
}, OUTGOING_MESSAGE_FILTER);
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() {
@ -181,8 +179,8 @@ public final class ChatManager extends Manager {
});
}
private boolean shouldAcceptMessage(Message message) {
if (!message.getBodies().isEmpty()) {
private boolean shouldAcceptMessage(MessageView message) {
if (message.hasExtension(Message.Body.QNAME)) {
return true;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus.
* 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.
@ -16,12 +16,12 @@
*/
package org.jivesoftware.smack.chat2;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jxmpp.jid.EntityBareJid;
public interface OutgoingChatMessageListener {
void newOutgoingMessage(EntityBareJid to, Message message, Chat chat);
void newOutgoingMessage(EntityBareJid to, MessageBuilder messageBuilder, Chat chat);
}

View File

@ -51,9 +51,11 @@ import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.PresenceBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.roster.AbstractPresenceEventListener;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64;
@ -308,6 +310,15 @@ public final class EntityCapsManager extends Manager {
*/
private String entityNode = DEFAULT_ENTITY_NODE;
// Intercept presence packages and add caps data when intended.
// XEP-0115 specifies that a client SHOULD include entity capabilities
// with every presence notification it sends.
private final Consumer<PresenceBuilder> presenceInterceptor = presenceBuilder -> {
CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
presenceBuilder.overrideExtension(caps);
};
private EntityCapsManager(XMPPConnection connection) {
super(connection);
this.sdm = ServiceDiscoveryManager.getInstanceFor(connection);
@ -379,23 +390,9 @@ public final class EntityCapsManager extends Manager {
}
}, PresenceTypeFilter.OUTGOING_PRESENCE_BROADCAST);
// Intercept presence packages and add caps data when intended.
// XEP-0115 specifies that a client SHOULD include entity capabilities
// with every presence notification it sends.
StanzaListener packetInterceptor = new StanzaListener() {
@Override
public void processStanza(Stanza packet) {
if (!entityCapsEnabled) {
// Be sure to not send stanzas with the caps extension if it's not enabled
packet.removeExtension(CapsExtension.ELEMENT, CapsExtension.NAMESPACE);
return;
}
CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
packet.overrideExtension(caps);
}
};
connection.addStanzaInterceptor(packetInterceptor, PresenceTypeFilter.AVAILABLE);
enableEntityCaps();
// It's important to do this as last action. Since it changes the
// behavior of the SDM in some ways
sdm.addEntityCapabilitiesChangedListener(new EntityCapabilitiesChangedListener() {
@ -424,6 +421,10 @@ public final class EntityCapsManager extends Manager {
}
public synchronized void enableEntityCaps() {
connection().addPresenceInterceptor(presenceInterceptor, p -> {
return PresenceTypeFilter.AVAILABLE.accept(p);
});
// Add Entity Capabilities (XEP-0115) feature node.
sdm.addFeature(NAMESPACE);
updateLocalEntityCaps();
@ -433,6 +434,8 @@ public final class EntityCapsManager extends Manager {
public synchronized void disableEntityCaps() {
entityCapsEnabled = false;
sdm.removeFeature(NAMESPACE);
connection().removePresenceInterceptor(presenceInterceptor);
}
public boolean entityCapsEnabled() {

View File

@ -37,11 +37,11 @@ import org.jivesoftware.smack.chat2.OutgoingChatMessageListener;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromTypeFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaBuilder;
@ -73,7 +73,6 @@ public final class ChatStateManager extends Manager {
private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>();
private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE));
private static final StanzaFilter INCOMING_MESSAGE_FILTER =
new AndFilter(MessageTypeFilter.NORMAL_OR_CHAT, FromTypeFilter.ENTITY_FULL_JID);
private static final StanzaFilter INCOMING_CHAT_STATE_FILTER = new AndFilter(INCOMING_MESSAGE_FILTER, new StanzaExtensionFilter(NAMESPACE));
@ -117,13 +116,13 @@ public final class ChatStateManager extends Manager {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addOutgoingListener(new OutgoingChatMessageListener() {
@Override
public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
public void newOutgoingMessage(EntityBareJid to, MessageBuilder message, Chat chat) {
if (chat == null) {
return;
}
// if message already has a chatStateExtension, then do nothing,
if (!filter.accept(message)) {
if (message.hasExtension(ChatStateExtension.NAMESPACE)) {
return;
}

View File

@ -358,7 +358,7 @@ public class MultiUserChat {
);
// @formatter:on
connection.addSyncStanzaListener(declinesListener, new AndFilter(fromRoomFilter, DECLINE_FILTER));
connection.addStanzaInterceptor(presenceInterceptor, new AndFilter(ToMatchesFilter.create(room),
connection.addStanzaSendingListener(presenceInterceptor, new AndFilter(ToMatchesFilter.create(room),
StanzaTypeFilter.PRESENCE));
messageCollector = connection.createStanzaCollector(fromRoomGroupchatFilter);
@ -2122,7 +2122,7 @@ public class MultiUserChat {
connection.removeSyncStanzaListener(presenceListener);
connection.removeSyncStanzaListener(subjectListener);
connection.removeSyncStanzaListener(declinesListener);
connection.removeStanzaInterceptor(presenceInterceptor);
connection.removeStanzaSendingListener(presenceInterceptor);
if (messageCollector != null) {
messageCollector.cancel();
messageCollector = null;

View File

@ -38,9 +38,11 @@ import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
@ -271,13 +273,7 @@ public final class DeliveryReceiptManager extends Manager {
);
// @formatter:on
private static final StanzaListener AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
Message message = (Message) packet;
DeliveryReceiptRequest.addTo(message);
}
};
private static final Consumer<MessageBuilder> AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = mb -> DeliveryReceiptRequest.addTo(mb);
/**
* Enables automatic requests of delivery receipts for outgoing messages of
@ -288,8 +284,9 @@ public final class DeliveryReceiptManager extends Manager {
* @see #dontAutoAddDeliveryReceiptRequests()
*/
public void autoAddDeliveryReceiptRequests() {
connection().addStanzaInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER,
MESSAGES_TO_REQUEST_RECEIPTS_FOR);
connection().addMessageInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER, m -> {
return MESSAGES_TO_REQUEST_RECEIPTS_FOR.accept(m);
});
}
/**
@ -299,7 +296,7 @@ public final class DeliveryReceiptManager extends Manager {
* @see #autoAddDeliveryReceiptRequests()
*/
public void dontAutoAddDeliveryReceiptRequests() {
connection().removeStanzaInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER);
connection().removeMessageInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER);
}
/**

View File

@ -21,8 +21,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageView;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
@ -40,6 +42,8 @@ public class XHTMLExtension implements ExtensionElement {
public static final String ELEMENT = "html";
public static final String NAMESPACE = "http://jabber.org/protocol/xhtml-im";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final List<CharSequence> bodies = new ArrayList<>();
/**
@ -125,7 +129,7 @@ public class XHTMLExtension implements ExtensionElement {
}
}
public static XHTMLExtension from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
public static XHTMLExtension from(MessageView message) {
return message.getExtension(QNAME);
}
}

View File

@ -17,6 +17,7 @@
package org.jivesoftware.smack.chat2;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.util.StringUtils;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -36,7 +37,8 @@ public class OutgoingMessageListenerIntegrationTest extends AbstractChatIntegrat
final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint();
final OutgoingChatMessageListener listener = new OutgoingChatMessageListener() {
@Override
public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
public void newOutgoingMessage(EntityBareJid to, MessageBuilder messageBuilder, Chat chat) {
Message message = messageBuilder.build();
if (message.getBody().equals(body)) {
syncPoint.signal();
}