1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-10 02:39:42 +02:00

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);
}