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 <reda.housniala...@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("jul...@capulet.lit/chamber"); - private static final Entity ROMEO_IN_ORCHARD = EntityImpl.parseUnchecked("ro...@montague.lit/orchard"); + private static final Entity ROMEO_IN_ORCHARD = EntityImpl.parseUnchecked("ro...@capulet.lit/orchard"); - private static final Entity MACBETH_IN_KITCHEN = EntityImpl.parseUnchecked("macb...@shakespeare.lit/kitchen"); + private static final Entity MACBETH_IN_KITCHEN = EntityImpl.parseUnchecked("macb...@capulet.lit/kitchen"); - private static final Entity ALICE_IN_RABBIT_HOLE = EntityImpl.parseUnchecked("al...@carol.lit/rabbit-hole"); + private static final Entity ALICE_IN_RABBIT_HOLE = EntityImpl.parseUnchecked("al...@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);