You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by ra...@apache.org on 2019/09/08 19:36:27 UTC

[mina-vysper] branch master updated: XEP-0313 MAM: take into account XEP-0334: Message Processing Hints

This is an automated email from the ASF dual-hosted git repository.

ralaoui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-vysper.git


The following commit(s) were added to refs/heads/master by this push:
     new 4e3e823  XEP-0313 MAM: take into account XEP-0334: Message Processing Hints
4e3e823 is described below

commit 4e3e823da2bc47567a3cc555199d04a188fe3707
Author: Réda Housni Alaoui <re...@gmail.com>
AuthorDate: Sun Sep 8 21:36:17 2019 +0200

    XEP-0313 MAM: take into account XEP-0334: Message Processing Hints
---
 .../apache/vysper/xmpp/protocol/NamespaceURIs.java |   2 +
 .../extension/xep0313_mam/MessageStanzaWithId.java |   6 +-
 .../interceptor/MAMStanzaBrokerProvider.java       |   2 +-
 .../interceptor/MAMStanzaHandlerInterceptor.java   |  63 ++++++++-
 .../muc/MUCMessageStanzaBrokerProvider.java        |   2 +-
 .../xep0313_mam/user/UserMessageStanzaBroker.java  |  49 ++++---
 .../user/UserMessageStanzaBrokerProvider.java      |   4 +-
 .../MAMStanzaHandlerInterceptorTest.java           | 149 +++++++++++++++++++++
 .../user/UserMessageStanzaBrokerTest.java          |  27 +++-
 9 files changed, 262 insertions(+), 42 deletions(-)

diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/protocol/NamespaceURIs.java b/server/core/src/main/java/org/apache/vysper/xmpp/protocol/NamespaceURIs.java
index d86a161..200726f 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/protocol/NamespaceURIs.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/protocol/NamespaceURIs.java
@@ -115,4 +115,6 @@ public class NamespaceURIs {
     public static final String XEP0297_STANZA_FORWARDING = "urn:xmpp:forward:0";
 
     public static final String XEP0359_STANZA_IDS = "urn:xmpp:sid:0";
+    
+    public static final String XEP0334_MESSAGE_PROCESSING_HINTS = "urn:xmpp:hints";
 }
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MessageStanzaWithId.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MessageStanzaWithId.java
index 73ad748..c003291 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MessageStanzaWithId.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MessageStanzaWithId.java
@@ -48,7 +48,7 @@ public class MessageStanzaWithId {
 
     public MessageStanzaWithId(ArchivedMessage archivedMessage, Entity archiveId) {
         this.archivedMessage = requireNonNull(archivedMessage);
-        this.archiveId = archiveId;
+        this.archiveId = requireNonNull(archiveId);
     }
 
     public Stanza toStanza() {
@@ -57,9 +57,7 @@ public class MessageStanzaWithId {
         List<XMLElement> innerElements = new ArrayList<>();
         archivedMessageStanza.getInnerElements().stream().filter(notStanzaId()).forEach(innerElements::add);
         List<Attribute> stanzaIdAttributes = new ArrayList<>();
-        if (archiveId != null) {
-            stanzaIdAttributes.add(new Attribute("by", archiveId.getFullQualifiedName()));
-        }
+        stanzaIdAttributes.add(new Attribute("by", archiveId.getFullQualifiedName()));
         stanzaIdAttributes.add(new Attribute("id", archivedMessage.id()));
         innerElements.add(new XMLElement(NamespaceURIs.XEP0359_STANZA_IDS, STANZA_ID, null, stanzaIdAttributes,
                 Collections.emptyList()));
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaBrokerProvider.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaBrokerProvider.java
index 788f647..6c571cc 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaBrokerProvider.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaBrokerProvider.java
@@ -32,6 +32,6 @@ public interface MAMStanzaBrokerProvider {
     boolean supports(Stanza processedStanza, ServerRuntimeContext serverRuntimeContext);
 
     StanzaBroker proxy(StanzaBroker delegate, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext,
-            boolean isOutboundStanza);
+                       boolean isOutboundStanza, boolean forceArchiving);
 
 }
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptor.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptor.java
index e535a79..b4653d7 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptor.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptor.java
@@ -23,8 +23,11 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xml.fragment.XMLSemanticError;
 import org.apache.vysper.xmpp.modules.extension.xep0313_mam.muc.MUCMessageStanzaBrokerProvider;
 import org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserMessageStanzaBrokerProvider;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
 import org.apache.vysper.xmpp.protocol.ProtocolException;
 import org.apache.vysper.xmpp.protocol.SessionStateHolder;
 import org.apache.vysper.xmpp.protocol.StanzaBroker;
@@ -32,6 +35,7 @@ import org.apache.vysper.xmpp.protocol.StanzaHandlerInterceptor;
 import org.apache.vysper.xmpp.protocol.StanzaHandlerInterceptorChain;
 import org.apache.vysper.xmpp.server.ServerRuntimeContext;
 import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
 import org.apache.vysper.xmpp.stanza.Stanza;
 
 /**
@@ -49,18 +53,67 @@ public class MAMStanzaHandlerInterceptor implements StanzaHandlerInterceptor {
         this.stanzaBrokerProviders = Collections.unmodifiableList(modifiableStanzaBrokerProviders);
     }
 
+    MAMStanzaHandlerInterceptor(List<MAMStanzaBrokerProvider> stanzaBrokerProviders) {
+        this.stanzaBrokerProviders = stanzaBrokerProviders;
+    }
+
     @Override
     public void intercept(Stanza stanza, ServerRuntimeContext serverRuntimeContext, boolean isOutboundStanza,
             SessionContext sessionContext, SessionStateHolder sessionStateHolder, StanzaBroker stanzaBroker,
             StanzaHandlerInterceptorChain interceptorChain) throws ProtocolException {
 
-        StanzaBroker stanzaBrokerToUse = stanzaBrokerProviders.stream()
-                .filter(mamStanzaBrokerProvider -> mamStanzaBrokerProvider.supports(stanza, serverRuntimeContext))
-                .map(mamStanzaBrokerProvider -> mamStanzaBrokerProvider.proxy(stanzaBroker, serverRuntimeContext,
-                        sessionContext, isOutboundStanza))
-                .findFirst().orElse(stanzaBroker);
+        StanzaBroker stanzaBrokerToUse;
+        if (isCandidateForArchiving(stanza)) {
+            stanzaBrokerToUse = stanzaBrokerProviders.stream()
+                    .filter(mamStanzaBrokerProvider -> mamStanzaBrokerProvider.supports(stanza, serverRuntimeContext))
+                    .map(mamStanzaBrokerProvider -> mamStanzaBrokerProvider.proxy(stanzaBroker, serverRuntimeContext,
+                            sessionContext, isOutboundStanza, isArchivingForced(stanza)))
+                    .findFirst().orElse(stanzaBroker);
+        } else {
+            stanzaBrokerToUse = stanzaBroker;
+        }
 
         interceptorChain.intercept(stanza, serverRuntimeContext, isOutboundStanza, sessionContext, sessionStateHolder,
                 stanzaBrokerToUse);
     }
+
+    private boolean isCandidateForArchiving(Stanza stanza) {
+        if (!MessageStanza.isOfType(stanza)) {
+            return false;
+        }
+
+        XMLElement noPermanentStore;
+        try {
+            noPermanentStore = stanza.getSingleInnerElementsNamed("no-permanent-store",
+                    NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS);
+        } catch (XMLSemanticError xmlSemanticError) {
+            noPermanentStore = null;
+        }
+
+        if (noPermanentStore != null) {
+            return false;
+        }
+
+        XMLElement noStore;
+        try {
+            noStore = stanza.getSingleInnerElementsNamed("no-store", NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS);
+        } catch (XMLSemanticError xmlSemanticError) {
+            noStore = null;
+        }
+
+        if (noStore != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isArchivingForced(Stanza stanza) {
+        try {
+            return stanza.getSingleInnerElementsNamed("store", NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS) != null;
+        } catch (XMLSemanticError xmlSemanticError) {
+            return false;
+        }
+    }
+
 }
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCMessageStanzaBrokerProvider.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCMessageStanzaBrokerProvider.java
index dabd17a..76e1fd6 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCMessageStanzaBrokerProvider.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCMessageStanzaBrokerProvider.java
@@ -47,7 +47,7 @@ public class MUCMessageStanzaBrokerProvider implements MAMStanzaBrokerProvider {
 
     @Override
     public StanzaBroker proxy(StanzaBroker delegate, ServerRuntimeContext serverRuntimeContext,
-            SessionContext sessionContext, boolean isOutboundStanza) {
+                              SessionContext sessionContext, boolean isOutboundStanza, boolean forceArchiving) {
         return new MUCMessageStanzaBroker(delegate, serverRuntimeContext, sessionContext, isOutboundStanza);
     }
 }
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBroker.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBroker.java
index 4fcc1bc..aef9583 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBroker.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBroker.java
@@ -21,10 +21,8 @@ package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user;
 
 import static java.util.Objects.requireNonNull;
 
-import java.util.Map;
 import java.util.Optional;
 
-import org.apache.vysper.xml.fragment.XMLElement;
 import org.apache.vysper.xml.fragment.XMLSemanticError;
 import org.apache.vysper.xmpp.addressing.Entity;
 import org.apache.vysper.xmpp.delivery.failure.DeliveryException;
@@ -58,12 +56,15 @@ class UserMessageStanzaBroker extends DelegatingStanzaBroker {
 
     private final boolean isOutbound;
 
+    private final boolean archivingForced;
+
     UserMessageStanzaBroker(StanzaBroker delegate, ServerRuntimeContext serverRuntimeContext,
-            SessionContext sessionContext, boolean isOutbound) {
+            SessionContext sessionContext, boolean isOutbound, boolean archivingForced) {
         super(delegate);
         this.serverRuntimeContext = requireNonNull(serverRuntimeContext);
         this.sessionContext = sessionContext;
         this.isOutbound = isOutbound;
+        this.archivingForced = archivingForced;
     }
 
     @Override
@@ -83,35 +84,39 @@ class UserMessageStanzaBroker extends DelegatingStanzaBroker {
         }
 
         MessageStanza messageStanza = new MessageStanza(stanza);
-        MessageStanzaType messageStanzaType = messageStanza.getMessageType();
-        if (messageStanzaType != MessageStanzaType.NORMAL && messageStanzaType != MessageStanzaType.CHAT) {
-            // A server SHOULD include in a user archive all of the messages a user sends
-            // or receives of type 'normal' or 'chat' that contain a <body> element.
-            LOG.debug("Message {} is neither of type 'normal' or 'chat'. It will not be archived.", messageStanza);
-            return messageStanza;
-        }
-
-        Map<String, XMLElement> bodies;
-        try {
-            bodies = messageStanza.getBodies();
-        } catch (XMLSemanticError xmlSemanticError) {
-            return messageStanza;
-        }
-        if (bodies.isEmpty()) {
-            // A server SHOULD include in a user archive all of the messages a user sends
-            // or receives of type 'normal' or 'chat' that contain a <body> element.
-            return messageStanza;
+        if (!shouldArchive(messageStanza)) {
+            return stanza;
         }
 
         // TODO Check preferences
         if (isOutbound) {
             addToSenderArchive(messageStanza, sessionContext);
-            return messageStanza;
+            return stanza;
         } else {
             return addToReceiverArchive(messageStanza).map(MessageStanzaWithId::toStanza).orElse(stanza);
         }
     }
 
+    private boolean shouldArchive(MessageStanza messageStanza) {
+        if (archivingForced) {
+            return true;
+        }
+
+        // A server SHOULD include in a user archive all of the messages a user sends
+        // or receives of type 'normal' or 'chat' that contain a <body> element.
+        MessageStanzaType messageStanzaType = messageStanza.getMessageType();
+        if (messageStanzaType != MessageStanzaType.NORMAL && messageStanzaType != MessageStanzaType.CHAT) {
+            LOG.debug("Message {} is neither of type 'normal' or 'chat'. It will not be archived.", messageStanza);
+            return false;
+        }
+
+        try {
+            return !messageStanza.getBodies().isEmpty();
+        } catch (XMLSemanticError xmlSemanticError) {
+            return false;
+        }
+    }
+
     private void addToSenderArchive(MessageStanza messageStanza, SessionContext sessionContext) {
         // Servers that expose archive messages of sent/received messages on behalf of
         // local users MUST expose these archives to the user on the user's bare JID.
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerProvider.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerProvider.java
index 970a991..94768db 100644
--- a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerProvider.java
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerProvider.java
@@ -37,8 +37,8 @@ public class UserMessageStanzaBrokerProvider implements MAMStanzaBrokerProvider
 
     @Override
     public StanzaBroker proxy(StanzaBroker delegate, ServerRuntimeContext serverRuntimeContext,
-            SessionContext sessionContext, boolean isOutboundStanza) {
+                              SessionContext sessionContext, boolean isOutboundStanza, boolean forceArchiving) {
         return new UserMessageStanzaBroker(delegate, serverRuntimeContext, sessionContext,
-				isOutboundStanza);
+				isOutboundStanza, forceArchiving);
     }
 }
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptorTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptorTest.java
new file mode 100644
index 0000000..e02e7c1
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/interceptor/MAMStanzaHandlerInterceptorTest.java
@@ -0,0 +1,149 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.vysper.xmpp.modules.extension.xep0313_mam.interceptor;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Collections;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.protocol.ProtocolException;
+import org.apache.vysper.xmpp.protocol.SessionStateHolder;
+import org.apache.vysper.xmpp.protocol.StanzaBroker;
+import org.apache.vysper.xmpp.protocol.StanzaHandlerInterceptorChain;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMStanzaHandlerInterceptorTest {
+
+    private static final Entity ALICE = EntityImpl.parseUnchecked("alice@foo.com");
+
+    private static final Entity BOB = EntityImpl.parseUnchecked("bob@foo.com");
+
+    private ServerRuntimeContext serverRuntimeContext;
+
+    private SessionContext sessionContext;
+
+    private SessionStateHolder sessionStateHolder;
+
+    private StanzaBroker nonArchivingStanzaBroker;
+
+    private StanzaBroker archivingStanzaBroker;
+
+    private StanzaHandlerInterceptorChain interceptorChain;
+
+    private MAMStanzaBrokerProviderMock mamStanzaBrokerProvider;
+
+    private MAMStanzaHandlerInterceptor tested;
+
+    @Before
+    public void before() {
+        serverRuntimeContext = mock(ServerRuntimeContext.class);
+        sessionContext = mock(SessionContext.class);
+        sessionStateHolder = mock(SessionStateHolder.class);
+        nonArchivingStanzaBroker = mock(StanzaBroker.class);
+        archivingStanzaBroker = mock(StanzaBroker.class);
+        interceptorChain = mock(StanzaHandlerInterceptorChain.class);
+
+        mamStanzaBrokerProvider = new MAMStanzaBrokerProviderMock();
+
+        tested = new MAMStanzaHandlerInterceptor(Collections.singletonList(mamStanzaBrokerProvider));
+    }
+
+    @Test
+    public void stanzaWithHintNoStoreShouldNotBeStored() throws ProtocolException {
+        Stanza stanza = StanzaBuilder.createMessageStanza(ALICE, BOB, "en", null)
+                .startInnerElement("no-store", NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS).endInnerElement()
+                .build();
+
+        tested.intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker, interceptorChain);
+
+        verify(interceptorChain).intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker);
+    }
+
+    @Test
+    public void stanzaWithHintNoPermanentStoreShouldNotBeStored() throws ProtocolException {
+        Stanza stanza = StanzaBuilder.createMessageStanza(ALICE, BOB, "en", null)
+                .startInnerElement("no-permanent-store", NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS)
+                .endInnerElement().build();
+
+        tested.intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker, interceptorChain);
+
+        verify(interceptorChain).intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker);
+    }
+
+    @Test
+    public void stanzaWithHintStoreShouldBeStored() throws ProtocolException {
+        Stanza stanza = StanzaBuilder.createMessageStanza(ALICE, BOB, "en", null)
+                .startInnerElement("store", NamespaceURIs.XEP0334_MESSAGE_PROCESSING_HINTS).endInnerElement().build();
+
+        tested.intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker, interceptorChain);
+
+        verify(interceptorChain).intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                archivingStanzaBroker);
+        assertTrue(mamStanzaBrokerProvider.archivingForced);
+    }
+
+    @Test
+    public void stanzaWithoutHintShouldBeStored() throws ProtocolException {
+        Stanza stanza = StanzaBuilder.createMessageStanza(ALICE, BOB, "en", null).build();
+
+        tested.intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                nonArchivingStanzaBroker, interceptorChain);
+
+        verify(interceptorChain).intercept(stanza, serverRuntimeContext, false, sessionContext, sessionStateHolder,
+                archivingStanzaBroker);
+        assertFalse(mamStanzaBrokerProvider.archivingForced);
+    }
+
+    private class MAMStanzaBrokerProviderMock implements MAMStanzaBrokerProvider {
+
+        private boolean archivingForced;
+
+        @Override
+        public boolean supports(Stanza processedStanza, ServerRuntimeContext serverRuntimeContext) {
+            return true;
+        }
+
+        @Override
+        public StanzaBroker proxy(StanzaBroker delegate, ServerRuntimeContext serverRuntimeContext,
+                SessionContext sessionContext, boolean isOutboundStanza, boolean forceArchiving) {
+            this.archivingForced = forceArchiving;
+            return archivingStanzaBroker;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerTest.java
index 2c88020..ac45a64 100644
--- a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerTest.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageStanzaBrokerTest.java
@@ -78,8 +78,9 @@ public class UserMessageStanzaBrokerTest {
         sessionContext.givenServerRuntimeContext(serverRuntimeContext);
     }
 
-    private UserMessageStanzaBroker buildTested(boolean isOutboundStanza) {
-        return new UserMessageStanzaBroker(delegate, serverRuntimeContext, sessionContext, isOutboundStanza);
+    private UserMessageStanzaBroker buildTested(boolean isOutboundStanza, boolean archivingForced) {
+        return new UserMessageStanzaBroker(delegate, serverRuntimeContext, sessionContext, isOutboundStanza,
+                archivingForced);
     }
 
     private MessageStanza buildMessageStanza(MessageStanzaType messageStanzaType, Entity from, Entity to, String body) {
@@ -88,7 +89,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Test
     public void onlyNormalAndChatMessageAreArchived() {
-        UserMessageStanzaBroker tested = buildTested(true);
+        UserMessageStanzaBroker tested = buildTested(true, false);
 
         Stream.of(MessageStanzaType.values()).filter(messageStanzaType -> messageStanzaType != MessageStanzaType.NORMAL)
                 .filter(messageStanzaType -> messageStanzaType != MessageStanzaType.CHAT).forEach(messageStanzaType -> {
@@ -116,7 +117,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Test
     public void outboundMessageHavingFrom() {
-        UserMessageStanzaBroker tested = buildTested(true);
+        UserMessageStanzaBroker tested = buildTested(true, false);
         MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, ROMEO_IN_ORCHARD,
                 ALICE_IN_RABBIT_HOLE, "hello world");
 
@@ -127,7 +128,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Test
     public void outboundMessageWithoutFrom() {
-        UserMessageStanzaBroker tested = buildTested(true);
+        UserMessageStanzaBroker tested = buildTested(true, false);
 
         MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, null, ALICE_IN_RABBIT_HOLE,
                 "hello world");
@@ -139,7 +140,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Test
     public void unexistingArchive() {
-        UserMessageStanzaBroker tested = buildTested(true);
+        UserMessageStanzaBroker tested = buildTested(true, false);
 
         MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, ALICE_IN_RABBIT_HOLE,
                 ALICE_IN_RABBIT_HOLE, "hello world");
@@ -153,7 +154,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Test
     public void messageWithoutBody() {
-        UserMessageStanzaBroker tested = buildTested(true);
+        UserMessageStanzaBroker tested = buildTested(true, false);
         MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, JULIET_IN_CHAMBER, ROMEO_IN_ORCHARD,
                 null);
 
@@ -162,5 +163,17 @@ public class UserMessageStanzaBrokerTest {
         julietArchive.assertEmpty();
         romeoArchive.assertEmpty();
     }
+    
+    @Test
+    public void messageWithoutBodyWithArchivingForced(){
+        UserMessageStanzaBroker tested = buildTested(true, true);
+        MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, JULIET_IN_CHAMBER, ROMEO_IN_ORCHARD,
+                null);
+
+        tested.writeToSession(messageStanza);
+
+        julietArchive.assertUniqueArchivedMessageStanza(messageStanza);
+        romeoArchive.assertEmpty();
+    }
 
 }
\ No newline at end of file