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/03 22:21:50 UTC

[mina-vysper] branch master updated: XEP-0313 Message Archive Management: Archive the message even if the receiver is offline

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 63cb357  XEP-0313 Message Archive Management: Archive the message even if the receiver is offline
63cb357 is described below

commit 63cb3574ef5bcb5a8db01b792129c3eb118a7871
Author: Réda Housni Alaoui <re...@gmail.com>
AuthorDate: Tue Sep 3 23:30:41 2019 +0200

    XEP-0313 Message Archive Management: Archive the message even if the receiver is offline
---
 .../xep0313_mam/user/UserMessageStanzaBroker.java  | 54 ++++++++++++--------
 .../extension/xep0313_mam/IntegrationTest.java     | 10 +++-
 .../xep0313_mam/ServerRuntimeContextMock.java      |  9 +++-
 .../ToggleableOfflineStorageProvider.java          | 59 ++++++++++++++++++++++
 .../extension/xep0313_mam/UserArchiveTest.java     | 44 ++++++++++++++--
 .../user/UserArchiveQueryHandlerTest.java          |  2 +-
 .../user/UserMessageStanzaBrokerTest.java          | 20 ++------
 7 files changed, 151 insertions(+), 47 deletions(-)

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 1539447..8d236b5 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
@@ -24,6 +24,7 @@ import static java.util.Objects.requireNonNull;
 import java.util.Optional;
 
 import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityUtils;
 import org.apache.vysper.xmpp.delivery.failure.DeliveryException;
 import org.apache.vysper.xmpp.delivery.failure.DeliveryFailureStrategy;
 import org.apache.vysper.xmpp.modules.core.base.handler.XMPPCoreStanzaHandler;
@@ -75,6 +76,9 @@ class UserMessageStanzaBroker extends DelegatingStanzaBroker {
     }
 
     private Stanza archive(Stanza stanza) {
+        if (!isOutbound) {
+            return stanza;
+        }
         if (!MessageStanza.isOfType(stanza)) {
             return stanza;
         }
@@ -88,33 +92,39 @@ class UserMessageStanzaBroker extends DelegatingStanzaBroker {
             return messageStanza;
         }
 
-        Entity archiveJID;
-        if (isOutbound) {
-            // We will store the message in the sender archive
-            archiveJID = XMPPCoreStanzaHandler.extractSenderJID(messageStanza, sessionContext);
-        } else {
-            // We will store the message in the receiver archive
-            archiveJID = requireNonNull(messageStanza.getTo(), "No 'to' found in " + messageStanza);
-        }
+        addToSenderArchive(messageStanza, sessionContext);
+        return addToReceiverArchive(messageStanza).map(MessageStanzaWithId::new).map(MessageStanzaWithId::toStanza)
+                .orElse(stanza);
+    }
 
+    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.
-        archiveJID = archiveJID.getBareJID();
-
-        MessageArchives archives = requireNonNull(serverRuntimeContext.getStorageProvider(MessageArchives.class),
-                "Could not find an instance of " + MessageArchives.class);
-
-        Optional<MessageArchive> userArchive = archives.retrieveUserMessageArchive(archiveJID);
-        if (!userArchive.isPresent()) {
-            LOG.debug("No archive returned for user with bare JID '{}'", archiveJID);
-            return messageStanza;
+        Entity senderArchiveId = XMPPCoreStanzaHandler.extractSenderJID(messageStanza, sessionContext).getBareJID();
+        Optional<MessageArchive> senderArchive = messageArchives().retrieveUserMessageArchive(senderArchiveId);
+        if (!senderArchive.isPresent()) {
+            LOG.debug("No archive returned for sender with bare JID '{}'", senderArchiveId);
+            return;
         }
+        senderArchive.get().archive(new SimpleMessage(messageStanza));
+    }
 
-        ArchivedMessage archivedMessage = userArchive.get().archive(new SimpleMessage(messageStanza));
-        if (isOutbound) {
-            return messageStanza;
-        } else {
-            return new MessageStanzaWithId(archivedMessage).toStanza();
+    private Optional<ArchivedMessage> addToReceiverArchive(MessageStanza messageStanza) {
+        Entity to = requireNonNull(messageStanza.getTo(), "No 'to' found in " + messageStanza);
+        if (!EntityUtils.isAddressingServer(to, serverRuntimeContext.getServerEntity())) {
+            LOG.debug("Receiver {} is not managed by this server", to);
+            return Optional.empty();
         }
+        // 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.
+        Entity receiverArchiveId = requireNonNull(messageStanza.getTo(), "No 'to' found in " + messageStanza)
+                .getBareJID();
+        return messageArchives().retrieveUserMessageArchive(receiverArchiveId)
+                .map(messageArchive -> messageArchive.archive(new SimpleMessage(messageStanza)));
+    }
+
+    private MessageArchives messageArchives() {
+        return requireNonNull(serverRuntimeContext.getStorageProvider(MessageArchives.class),
+                "Could not find an instance of " + MessageArchives.class);
     }
 }
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/IntegrationTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/IntegrationTest.java
index 648e9e2..32b21a1 100644
--- a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/IntegrationTest.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/IntegrationTest.java
@@ -30,7 +30,6 @@ import org.apache.vysper.storage.inmemory.MemoryStorageProviderRegistry;
 import org.apache.vysper.xmpp.addressing.EntityImpl;
 import org.apache.vysper.xmpp.authentication.AccountManagement;
 import org.apache.vysper.xmpp.cryptography.NonCheckingX509TrustManagerFactory;
-import org.apache.vysper.xmpp.modules.extension.xep0160_offline_storage.MemoryOfflineStorageProvider;
 import org.apache.vysper.xmpp.modules.extension.xep0313_mam.in_memory.InMemoryMessageArchives;
 import org.apache.vysper.xmpp.server.XMPPServer;
 import org.jivesoftware.smack.AbstractXMPPConnection;
@@ -77,9 +76,12 @@ public abstract class IntegrationTest {
 
     private XMPPServer server;
 
+    private ToggleableOfflineStorageProvider offlineStorageProvider;
+
     @Before
     public void setUp() throws Exception {
         SmackConfiguration.setDefaultReplyTimeout(5000);
+        offlineStorageProvider = new ToggleableOfflineStorageProvider();
 
         int port = findFreePort();
 
@@ -97,6 +99,10 @@ public abstract class IntegrationTest {
         return carolClient;
     }
 
+    protected ToggleableOfflineStorageProvider offlineStorageProvider() {
+        return offlineStorageProvider;
+    }
+
     protected Stanza sendSync(XMPPConnection client, Stanza request)
             throws SmackException.NotConnectedException, InterruptedException {
         StanzaCollector collector = client.createStanzaCollector(new StanzaIdFilter(request.getStanzaId()));
@@ -113,7 +119,7 @@ public abstract class IntegrationTest {
         accountManagement.addUser(EntityImpl.parseUnchecked(ALICE_USERNAME), PASSWORD);
         accountManagement.addUser(EntityImpl.parseUnchecked(CAROL_USERNAME), PASSWORD);
 
-        providerRegistry.add(new MemoryOfflineStorageProvider());
+        providerRegistry.add(offlineStorageProvider);
 
         server = new XMPPServer(SERVER_DOMAIN);
 
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
index 3130dab..c42043e 100644
--- a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
@@ -19,6 +19,7 @@
  */
 package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
 
+import static java.util.Objects.requireNonNull;
 import static org.mockito.Mockito.mock;
 
 import java.util.Collections;
@@ -47,9 +48,15 @@ import org.apache.vysper.xmpp.state.resourcebinding.ResourceRegistry;
  * @author Réda Housni Alaoui
  */
 public class ServerRuntimeContextMock implements ServerRuntimeContext {
+    
+    private final Entity serverEntity;
 
     private MessageArchivesMock userMessageArchives;
 
+    public ServerRuntimeContextMock(Entity serverEntity) {
+        this.serverEntity = requireNonNull(serverEntity);
+    }
+
     public MessageArchivesMock givenUserMessageArchives() {
         userMessageArchives = new MessageArchivesMock();
         return userMessageArchives;
@@ -67,7 +74,7 @@ public class ServerRuntimeContextMock implements ServerRuntimeContext {
 
     @Override
     public Entity getServerEntity() {
-        throw new UnsupportedOperationException();
+        return serverEntity;
     }
 
     @Override
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ToggleableOfflineStorageProvider.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ToggleableOfflineStorageProvider.java
new file mode 100644
index 0000000..0fea747
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ToggleableOfflineStorageProvider.java
@@ -0,0 +1,59 @@
+/*
+ *  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;
+
+import java.util.Collection;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0160_offline_storage.MemoryOfflineStorageProvider;
+import org.apache.vysper.xmpp.modules.extension.xep0160_offline_storage.OfflineStorageProvider;
+import org.apache.vysper.xmpp.stanza.Stanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ToggleableOfflineStorageProvider implements OfflineStorageProvider {
+
+    private final MemoryOfflineStorageProvider memoryOfflineStorageProvider;
+
+    private boolean disabled;
+
+    public void disable() {
+        disabled = true;
+    }
+
+    public ToggleableOfflineStorageProvider() {
+        this.memoryOfflineStorageProvider = new MemoryOfflineStorageProvider();
+    }
+
+    @Override
+    public Collection<Stanza> getStanzasFor(Entity jid) {
+        return memoryOfflineStorageProvider.getStanzasFor(jid);
+    }
+
+    @Override
+    public void receive(Stanza stanza) {
+        if (disabled) {
+            return;
+        }
+        memoryOfflineStorageProvider.receive(stanza);
+    }
+
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/UserArchiveTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/UserArchiveTest.java
index 3908aad..0e5bf4d 100644
--- a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/UserArchiveTest.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/UserArchiveTest.java
@@ -22,6 +22,7 @@ package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -31,6 +32,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
 import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.jivesoftware.smack.AbstractXMPPConnection;
 import org.jivesoftware.smack.SmackException;
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.chat2.Chat;
@@ -164,7 +166,8 @@ public class UserArchiveTest extends IntegrationTest {
     }
 
     @Test
-    public void sendMessageToOfflineReceiver() throws SmackException, InterruptedException, XMPPException, IOException {
+    public void givenOfflineStorageSendMessageToOfflineReceiver()
+            throws SmackException, InterruptedException, XMPPException, IOException {
         carol().instantShutdown();
 
         Chat chatFromAliceToCarol = ChatManager.getInstanceFor(alice()).chatWith(carol().getUser().asEntityBareJid());
@@ -182,10 +185,8 @@ public class UserArchiveTest extends IntegrationTest {
         assertNotNull(carolReceivedMessage.get());
         assertEquals("Hello carol", carolReceivedMessage.get().getBody());
 
-        MamManager.MamQueryArgs archiveFullQuery = MamManager.MamQueryArgs.builder().build();
-        MamManager.MamQuery carolArchive = MamManager.getInstanceFor(carol()).queryArchive(archiveFullQuery);
-        assertEquals(1, carolArchive.getMessageCount());
-        String storedStanzaId = extractStanzaId(carolArchive.getMessages().get(0));
+        Message archivedMessage = fetchUniqueArchivedMessage(carol());
+        String storedStanzaId = extractStanzaId(archivedMessage);
         assertNotNull(storedStanzaId);
 
         String receivedStanzaId = extractStanzaId(carolReceivedMessage.get());
@@ -193,6 +194,39 @@ public class UserArchiveTest extends IntegrationTest {
         assertEquals(storedStanzaId, receivedStanzaId);
     }
 
+    @Test
+    public void givenDisabledOfflineStorageSendMessageToOfflineReceiver()
+            throws SmackException, InterruptedException, XMPPException, IOException {
+        offlineStorageProvider().disable();
+        carol().instantShutdown();
+
+        Chat chatFromAliceToCarol = ChatManager.getInstanceFor(alice()).chatWith(carol().getUser().asEntityBareJid());
+        chatFromAliceToCarol.send("Hello carol");
+
+        AtomicReference<Message> carolReceivedMessage = new AtomicReference<>();
+        ChatManager.getInstanceFor(carol())
+                .addIncomingListener((from, message, chat) -> carolReceivedMessage.set(message));
+
+        carol().connect();
+        carol().login();
+
+        Thread.sleep(200);
+
+        assertNull(carolReceivedMessage.get());
+
+        Message message = fetchUniqueArchivedMessage(carol());
+        assertEquals("Hello carol", message.getBody());
+    }
+
+    private Message fetchUniqueArchivedMessage(AbstractXMPPConnection connection)
+            throws XMPPException.XMPPErrorException, InterruptedException, SmackException.NotConnectedException,
+            SmackException.NotLoggedInException, SmackException.NoResponseException {
+        MamManager.MamQueryArgs archiveFullQuery = MamManager.MamQueryArgs.builder().build();
+        MamManager.MamQuery archive = MamManager.getInstanceFor(connection).queryArchive(archiveFullQuery);
+        assertEquals(1, archive.getMessageCount());
+        return archive.getMessages().get(0);
+    }
+
     private String extractStanzaId(Stanza stanza) {
         assertNotNull(stanza);
         ExtensionElement extensionElement = stanza.getExtension(NamespaceURIs.XEP0359_STANZA_IDS);
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandlerTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandlerTest.java
index 0150861..bd97a08 100644
--- a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandlerTest.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandlerTest.java
@@ -80,7 +80,7 @@ public class UserArchiveQueryHandlerTest {
                         XMLParserUtil.parseRequiredDocument("<iq type='set'><query xmlns='urn:xmpp:mam:2'/></iq>"),
                         true, Collections.emptyList()).build()));
 
-        serverRuntimeContext = new ServerRuntimeContextMock();
+        serverRuntimeContext = new ServerRuntimeContextMock(EntityImpl.parseUnchecked("capulet.lit"));
         archives = serverRuntimeContext.givenUserMessageArchives();
         sessionContext = new SessionContextMock();
         sessionContext.givenInitiatingEntity(INITIATING_ENTITY);
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 e197a07..1c0d868 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
@@ -20,7 +20,6 @@
 package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user;
 
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import java.util.stream.Stream;
 
@@ -44,11 +43,11 @@ public class UserMessageStanzaBrokerTest {
 
     private static final Entity JULIET_IN_CHAMBER = EntityImpl.parseUnchecked("juliet@capulet.lit/chamber");
 
-    private static final Entity ROMEO_IN_ORCHARD = EntityImpl.parseUnchecked("romeo@montague.lit/orchard");
+    private static final Entity ROMEO_IN_ORCHARD = EntityImpl.parseUnchecked("romeo@capulet.lit/orchard");
 
-    private static final Entity MACBETH_IN_KITCHEN = EntityImpl.parseUnchecked("macbeth@shakespeare.lit/kitchen");
+    private static final Entity MACBETH_IN_KITCHEN = EntityImpl.parseUnchecked("macbeth@capulet.lit/kitchen");
 
-    private static final Entity ALICE_IN_RABBIT_HOLE = EntityImpl.parseUnchecked("alice@carol.lit/rabbit-hole");
+    private static final Entity ALICE_IN_RABBIT_HOLE = EntityImpl.parseUnchecked("alice@capulet.lit/rabbit-hole");
 
     private static final Entity INITIATING_ENTITY = JULIET_IN_CHAMBER;
 
@@ -66,7 +65,7 @@ public class UserMessageStanzaBrokerTest {
 
     @Before
     public void before() {
-        serverRuntimeContext = new ServerRuntimeContextMock();
+        serverRuntimeContext = new ServerRuntimeContextMock(EntityImpl.parseUnchecked("capulet.lit"));
 
         MessageArchivesMock archives = serverRuntimeContext.givenUserMessageArchives();
 
@@ -137,17 +136,6 @@ public class UserMessageStanzaBrokerTest {
     }
 
     @Test
-    public void inboundMessage() {
-        UserMessageStanzaBroker tested = buildTested(false);
-
-        MessageStanza messageStanza = buildMessageStanza(MessageStanzaType.NORMAL, null, MACBETH_IN_KITCHEN);
-
-        tested.writeToSession(messageStanza);
-
-        macbethArchive.assertUniqueArchivedMessageStanza(messageStanza);
-    }
-
-    @Test
     public void unexistingArchive() {
         UserMessageStanzaBroker tested = buildTested(true);