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/08/18 14:06:58 UTC
[mina-vysper] branch xep-0313 updated (494224f -> 422375b)
This is an automated email from the ASF dual-hosted git repository.
ralaoui pushed a change to branch xep-0313
in repository https://gitbox.apache.org/repos/asf/mina-vysper.git.
discard 494224f XEP-0313 Message Archive Management
new 422375b XEP-0313 Message Archive Management
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (494224f)
\
N -- N -- N refs/heads/xep-0313 (422375b)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../extension/xep0313_mam/query/MAMIQHandler.java | 11 +-
.../xep0313_mam/query/MAMIQHandlerTest.java | 139 +++++++++++++++++++++
.../query/MatchingArchivedMessageResultsTest.java | 1 -
3 files changed, 147 insertions(+), 4 deletions(-)
create mode 100644 server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandlerTest.java
[mina-vysper] 01/01: XEP-0313 Message Archive Management
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
ralaoui pushed a commit to branch xep-0313
in repository https://gitbox.apache.org/repos/asf/mina-vysper.git
commit 422375be20d0d7afe6e06097a4d6bb105dee7098
Author: Réda Housni Alaoui <re...@gmail.com>
AuthorDate: Tue Aug 6 13:54:52 2019 +0200
XEP-0313 Message Archive Management
---
.../vysper/xmpp/datetime/DateTimeProfile.java | 10 ++
.../core/base/handler/AcceptedMessageEvent.java | 75 ++++++++++
.../modules/core/base/handler/MessageHandler.java | 27 ++--
.../xep0059_result_set_management/Set.java | 49 +++---
.../apache/vysper/xmpp/parser/XMLParserUtil.java | 5 +
.../apache/vysper/xmpp/protocol/NamespaceURIs.java | 2 +
.../apache/vysper/xmpp/stanza/StanzaBuilder.java | 4 +-
.../xmpp/stanza/dataforms/DataFormParser.java | 4 +-
.../ResultSetManagementTest.java | 12 +-
.../stanza/dataforms/DataFormParserTestCase.java | 4 +-
server/extensions/pom.xml | 4 +-
server/extensions/xep0313-mam/pom.xml | 70 +++++++++
.../xep0313_mam/MAMEventListenerDictionary.java | 38 +++++
.../modules/extension/xep0313_mam/MAMModule.java | 103 +++++++++++++
.../extension/xep0313_mam/MAMNamespaceURIs.java | 33 ++++
.../extension/xep0313_mam/SimpleMessage.java | 57 +++++++
.../xep0313_mam/muc/MUCArchiveQueryHandler.java | 58 +++++++
.../pubsub/PubsubNodeArchiveQueryHandler.java | 54 +++++++
.../xep0313_mam/query/ArchiveEntityFilter.java | 79 ++++++++++
.../extension/xep0313_mam/query/ArchiveQuery.java | 59 ++++++++
.../extension/xep0313_mam/query/MAMIQHandler.java | 83 +++++++++++
.../xep0313_mam/query/MAMQueryHandler.java | 38 +++++
.../query/MatchingArchivedMessageResult.java | 109 ++++++++++++++
.../query/MatchingArchivedMessageResults.java | 89 +++++++++++
.../modules/extension/xep0313_mam/query/Query.java | 80 ++++++++++
.../extension/xep0313_mam/query/QuerySet.java | 65 ++++++++
.../modules/extension/xep0313_mam/query/X.java | 84 +++++++++++
.../extension/xep0313_mam/spi/ArchivedMessage.java | 34 +++++
.../xep0313_mam/spi/ArchivedMessages.java | 51 +++++++
.../extension/xep0313_mam/spi/DateTimeFilter.java | 48 ++++++
.../extension/xep0313_mam/spi/EntityFilter.java | 56 +++++++
.../modules/extension/xep0313_mam/spi/Message.java | 35 +++++
.../extension/xep0313_mam/spi/MessageArchive.java | 43 ++++++
.../extension/xep0313_mam/spi/MessageArchives.java | 34 +++++
.../xep0313_mam/spi/MessagePageFilter.java | 36 +++++
.../xep0313_mam/spi/SimpleArchivedMessage.java | 60 ++++++++
.../xep0313_mam/spi/SimpleArchivedMessages.java | 74 +++++++++
.../xep0313_mam/user/UserArchiveQueryHandler.java | 93 ++++++++++++
.../xep0313_mam/user/UserMessageListener.java | 81 ++++++++++
.../extension/xep0313_mam/MAMModuleTest.java | 82 ++++++++++
.../xep0313_mam/ServerRuntimeContextMock.java | 166 +++++++++++++++++++++
.../extension/xep0313_mam/SessionContextMock.java | 145 ++++++++++++++++++
.../extension/xep0313_mam/StanzaAssert.java | 37 +++++
.../xep0313_mam/query/ArchiveEntityFilterTest.java | 68 +++++++++
.../xep0313_mam/query/ArchiveQueryTest.java | 81 ++++++++++
.../xep0313_mam/query/MAMIQHandlerTest.java | 139 +++++++++++++++++
.../query/MatchingArchivedMessageResultsTest.java | 147 ++++++++++++++++++
.../extension/xep0313_mam/query/QuerySetTest.java} | 25 ++--
.../xep0313_mam/spi/MessageArchiveMock.java | 61 ++++++++
.../xep0313_mam/spi/MessageArchivesMock.java | 45 ++++++
.../xep0313_mam/user/UserMessageListenerTest.java | 127 ++++++++++++++++
51 files changed, 3004 insertions(+), 59 deletions(-)
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/datetime/DateTimeProfile.java b/server/core/src/main/java/org/apache/vysper/xmpp/datetime/DateTimeProfile.java
index 21377b4..aefe644 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/datetime/DateTimeProfile.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/datetime/DateTimeProfile.java
@@ -22,6 +22,7 @@ package org.apache.vysper.xmpp.datetime;
import static org.apache.vysper.compliance.SpecCompliant.ComplianceCoverage.COMPLETE;
import static org.apache.vysper.compliance.SpecCompliant.ComplianceStatus.IN_PROGRESS;
+import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
@@ -80,6 +81,10 @@ public class DateTimeProfile {
public String getDateTimeInUTC(Date time) {
return utcDateTimeFormatter.format(time);
}
+
+ public String getDateTimeInUTC(ZonedDateTime dateTime){
+ return getDateTimeInUTC(Date.from(dateTime.toInstant()));
+ }
public String getDateInUTC(Date time) {
return utcDateFormatter.format(time);
@@ -122,6 +127,11 @@ public class DateTimeProfile {
throw new IllegalArgumentException("Invalid date time: " + time);
}
}
+
+ public ZonedDateTime fromZonedDateTime(String time){
+ Calendar calendar = fromDateTime(time);
+ return ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
+ }
/**
* Parses a time, compliant with ISO-8601 and XEP-0082.
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/AcceptedMessageEvent.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/AcceptedMessageEvent.java
new file mode 100644
index 0000000..a81426c
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/AcceptedMessageEvent.java
@@ -0,0 +1,75 @@
+/*
+ * 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.core.base.handler;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class AcceptedMessageEvent {
+
+ private final ServerRuntimeContext serverRuntimeContext;
+
+ private final SessionContext sessionContext;
+
+ private final boolean outbound;
+
+ private final MessageStanza messageStanza;
+
+ public AcceptedMessageEvent(ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext,
+ boolean outbound, MessageStanza messageStanza) {
+ this.serverRuntimeContext = requireNonNull(serverRuntimeContext);
+ this.sessionContext = requireNonNull(sessionContext);
+ this.outbound = outbound;
+ this.messageStanza = requireNonNull(messageStanza);
+ }
+
+ public ServerRuntimeContext serverRuntimeContext() {
+ return serverRuntimeContext;
+ }
+
+ public SessionContext sessionContext() {
+ return sessionContext;
+ }
+
+ public boolean isOutbound() {
+ return outbound;
+ }
+
+ public MessageStanza messageStanza() {
+ return messageStanza;
+ }
+
+ @Override
+ public String toString() {
+ return "AcceptedMessageEvent{" + "serverRuntimeContext=" + serverRuntimeContext + ", sessionContext="
+ + sessionContext + ", outbound=" + outbound + ", messageStanza=" + messageStanza + '}';
+ }
+
+
+}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/MessageHandler.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/MessageHandler.java
index c96d991..c3a37b9 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/MessageHandler.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/core/base/handler/MessageHandler.java
@@ -20,6 +20,7 @@
package org.apache.vysper.xmpp.modules.core.base.handler;
+import org.apache.vysper.event.EventBus;
import org.apache.vysper.xml.fragment.Attribute;
import org.apache.vysper.xml.fragment.XMLElement;
import org.apache.vysper.xml.fragment.XMLSemanticError;
@@ -42,6 +43,7 @@ import java.util.List;
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class MessageHandler extends XMPPCoreStanzaHandler {
+
public String getName() {
return "message";
}
@@ -89,13 +91,8 @@ public class MessageHandler extends XMPPCoreStanzaHandler {
// TODO inspect all BODY elements and make sure they conform to the spec
+ EventBus eventBus = serverRuntimeContext.getEventBus();
if (isOutboundStanza) {
- // check if message reception is turned of either globally or locally
- if (!serverRuntimeContext.getServerFeatures().isRelayingMessages()
- || (sessionContext != null && sessionContext
- .getAttribute(SessionContext.SESSION_ATTRIBUTE_MESSAGE_STANZA_NO_RECEIVE) != null)) {
- return null;
- }
Entity from = stanza.getFrom();
if (from == null || !from.isResourceSet()) {
@@ -118,17 +115,27 @@ public class MessageHandler extends XMPPCoreStanzaHandler {
stanza = XMPPCoreStanza.getWrapper(stanzaBuilder.build());
}
+ eventBus.publish(AcceptedMessageEvent.class,
+ new AcceptedMessageEvent(serverRuntimeContext, sessionContext, true, new MessageStanza(stanza)));
+
+ // check if message reception is turned of either globally or locally
+ if (!serverRuntimeContext.getServerFeatures().isRelayingMessages()
+ || (sessionContext != null && sessionContext
+ .getAttribute(SessionContext.SESSION_ATTRIBUTE_MESSAGE_STANZA_NO_RECEIVE) != null)) {
+ return null;
+ }
+
StanzaRelay stanzaRelay = serverRuntimeContext.getStanzaRelay();
try {
stanzaRelay.relay(stanza.getTo(), stanza, new ReturnErrorToSenderFailureStrategy(stanzaRelay));
} catch (Exception e) {
// TODO return error stanza
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ e.printStackTrace(); // To change body of catch statement use File | Settings | File Templates.
}
- } else if (sessionContext != null) {
- sessionContext.getResponseWriter().write(stanza);
} else {
- throw new IllegalStateException("handling offline messages not implemented");
+ eventBus.publish(AcceptedMessageEvent.class,
+ new AcceptedMessageEvent(serverRuntimeContext, sessionContext, false, new MessageStanza(stanza)));
+ sessionContext.getResponseWriter().write(stanza);
}
return null;
}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/Set.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/Set.java
index 680ce68..6f306fd 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/Set.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/Set.java
@@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.OptionalInt;
import org.apache.vysper.xml.fragment.Attribute;
import org.apache.vysper.xml.fragment.XMLElement;
@@ -44,7 +43,7 @@ import org.apache.vysper.xmpp.protocol.NamespaceURIs;
*/
public class Set {
- private static final String ELEMENT_NAME = "set";
+ public static final String ELEMENT_NAME = "set";
private static final String FIRST = "first";
@@ -64,6 +63,15 @@ public class Set {
this.element = element;
}
+ public static Set empty() {
+ return new Set(new XMLElement(NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT, ELEMENT_NAME, null,
+ Collections.emptyList(), Collections.emptyList()));
+ }
+
+ public XMLElement element() {
+ return element;
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -76,24 +84,24 @@ public class Set {
return getElementText("before");
}
- public OptionalInt getCount() throws XMLSemanticError {
- return getElementInt("count");
+ public Optional<Long> getCount() throws XMLSemanticError {
+ return getElementLong("count");
}
public Optional<First> getFirst() throws XMLSemanticError {
return ofNullable(element.getSingleInnerElementsNamed(FIRST)).map(First::new);
}
- public OptionalInt getIndex() throws XMLSemanticError {
- return getElementInt(INDEX);
+ public Optional<Long> getIndex() throws XMLSemanticError {
+ return getElementLong(INDEX);
}
public Optional<String> getLast() throws XMLSemanticError {
return getElementText("last");
}
- public OptionalInt getMax() throws XMLSemanticError {
- return getElementInt("max");
+ public Optional<Long> getMax() throws XMLSemanticError {
+ return getElementLong("max");
}
private Optional<String> getElementText(String elementName) throws XMLSemanticError {
@@ -101,8 +109,8 @@ public class Set {
.map(XMLText::getText);
}
- private OptionalInt getElementInt(String elementName) throws XMLSemanticError {
- return getElementText(elementName).map(Integer::parseInt).map(OptionalInt::of).orElse(OptionalInt.empty());
+ private Optional<Long> getElementLong(String elementName) throws XMLSemanticError {
+ return getElementText(elementName).map(Long::parseLong);
}
public static class First {
@@ -121,9 +129,8 @@ public class Set {
return element.getInnerText().getText();
}
- public OptionalInt getIndex() {
- return ofNullable(element.getAttributeValue(INDEX)).map(Integer::parseInt).map(OptionalInt::of)
- .orElse(OptionalInt.empty());
+ public Optional<Long> getIndex() {
+ return ofNullable(element.getAttributeValue(INDEX)).map(Long::parseLong);
}
}
@@ -134,15 +141,15 @@ public class Set {
private String before;
- private Integer count;
+ private Long count;
private First first;
- private Integer index;
+ private Long index;
private String last;
- private Integer max;
+ private Long max;
private Builder() {
@@ -158,7 +165,7 @@ public class Set {
return this;
}
- public Builder count(Integer count) {
+ public Builder count(Long count) {
this.count = count;
return this;
}
@@ -167,7 +174,7 @@ public class Set {
return new FirstBuilder(this);
}
- public Builder index(Integer index) {
+ public Builder index(Long index) {
this.index = index;
return this;
}
@@ -177,7 +184,7 @@ public class Set {
return this;
}
- public Builder max(Integer max) {
+ public Builder max(Long max) {
this.max = max;
return this;
}
@@ -209,7 +216,7 @@ public class Set {
private String value;
- private Integer index;
+ private Long index;
private FirstBuilder(Builder builder) {
this.builder = requireNonNull(builder);
@@ -220,7 +227,7 @@ public class Set {
return this;
}
- public FirstBuilder index(Integer index) {
+ public FirstBuilder index(Long index) {
this.index = index;
return this;
}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/parser/XMLParserUtil.java b/server/core/src/main/java/org/apache/vysper/xmpp/parser/XMLParserUtil.java
index 1487ca0..cd59af2 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/parser/XMLParserUtil.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/parser/XMLParserUtil.java
@@ -33,6 +33,8 @@ import org.apache.vysper.xml.sax.NonBlockingXMLReader;
import org.apache.vysper.xml.sax.impl.DefaultNonBlockingXMLReader;
import org.xml.sax.SAXException;
+import static java.util.Optional.ofNullable;
+
/**
*
* @author The Apache MINA Project (dev@mina.apache.org)
@@ -73,5 +75,8 @@ public class XMLParserUtil {
}
}
+ public static XMLElement parseRequiredDocument(String xml) throws IOException, SAXException {
+ return ofNullable(parseDocument(xml)).orElseThrow(() -> new IllegalStateException("Parsed element should not be null"));
+ }
}
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 56bc78c..fd7923b 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
@@ -111,4 +111,6 @@ public class NamespaceURIs {
public static final String XEP0133_SERVICE_ADMIN = "http://jabber.org/protocol/admin";
public static final String XEP0059_RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm";
+
+ public static final String XEP0297_STANZA_FORWARDING = "urn:xmpp:forward:0";
}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/stanza/StanzaBuilder.java b/server/core/src/main/java/org/apache/vysper/xmpp/stanza/StanzaBuilder.java
index da697a1..fb28a97 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/stanza/StanzaBuilder.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/stanza/StanzaBuilder.java
@@ -55,7 +55,9 @@ public class StanzaBuilder extends AbstractXMLElementBuilder<StanzaBuilder, Stan
public static StanzaBuilder createMessageStanza(Entity from, Entity to, String lang, String body) {
StanzaBuilder stanzaBuilder = new StanzaBuilder("message", NamespaceURIs.JABBER_CLIENT);
- stanzaBuilder.addAttribute("from", from.getFullQualifiedName());
+ if (from != null) {
+ stanzaBuilder.addAttribute("from", from.getFullQualifiedName());
+ }
stanzaBuilder.addAttribute("to", to.getFullQualifiedName());
if (lang != null)
stanzaBuilder.addAttribute(NamespaceURIs.XML, "lang", lang);
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParser.java b/server/core/src/main/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParser.java
index 0682bca..6efd971 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParser.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParser.java
@@ -76,7 +76,7 @@ public class DataFormParser {
public Map<String, Object> extractFieldValues() throws IllegalArgumentException {
Map<String,Object> map = new LinkedHashMap<String, Object>();
- for (XMLElement fields : form.getInnerElementsNamed("field", NamespaceURIs.JABBER_X_DATA)) {
+ for (XMLElement fields : form.getInnerElementsNamed("field")) {
final String varName = fields.getAttributeValue("var");
final String typeName = fields.getAttributeValue("type");
String valueAsString = null;
@@ -93,7 +93,7 @@ public class DataFormParser {
boolean isMulti = Field.Type.isMulti(fieldType);
List<Object> values = isMulti ? new ArrayList<Object>() : null;
- for (XMLElement valueCandidates : fields.getInnerElementsNamed("value", NamespaceURIs.JABBER_X_DATA)) {
+ for (XMLElement valueCandidates : fields.getInnerElementsNamed("value")) {
final XMLText firstInnerText = valueCandidates.getFirstInnerText();
if (firstInnerText != null) valueAsString = firstInnerText.getText();
Object value;
diff --git a/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java b/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
index c52dfd6..5049e14 100644
--- a/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
+++ b/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
@@ -31,19 +31,19 @@ public class ResultSetManagementTest {
@Test
public void parse() throws XMLSemanticError {
- Set tested = Set.builder().after("after-value").before("before-value").count(5)
- .index(10).last("last-value").max(15).startFirst().index(20).value("first-value").endFirst().build();
+ Set tested = Set.builder().after("after-value").before("before-value").count(5L).index(10L).last("last-value")
+ .max(15L).startFirst().index(20L).value("first-value").endFirst().build();
assertEquals("after-value", tested.getAfter().get());
assertEquals("before-value", tested.getBefore().get());
- assertEquals(5, tested.getCount().getAsInt());
- assertEquals(10, tested.getIndex().getAsInt());
+ assertEquals(5L, (long) tested.getCount().orElse(0L));
+ assertEquals(10L, (long) tested.getIndex().orElse(0L));
assertEquals("last-value", tested.getLast().get());
- assertEquals(15, tested.getMax().getAsInt());
+ assertEquals(15L, (long) tested.getMax().orElse(0L));
Set.First first = tested.getFirst().get();
assertEquals("first-value", first.getValue());
- assertEquals(20, first.getIndex().getAsInt());
+ assertEquals(20L, (long) first.getIndex().orElse(0L));
}
}
\ No newline at end of file
diff --git a/server/core/src/test/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParserTestCase.java b/server/core/src/test/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParserTestCase.java
index 650ce83..a3c92a0 100644
--- a/server/core/src/test/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParserTestCase.java
+++ b/server/core/src/test/java/org/apache/vysper/xmpp/stanza/dataforms/DataFormParserTestCase.java
@@ -134,14 +134,14 @@ public class DataFormParserTestCase {
private void assertMultiValue(String type, List<String> values, List<Object> expectedValues) {
XMLElementBuilder builder = new XMLElementBuilder("x", NamespaceURIs.JABBER_X_DATA)
- .startInnerElement("field", NamespaceURIs.JABBER_X_DATA)
+ .startInnerElement("field")
.addAttribute("var", "fie1");
if(type != null) builder.addAttribute("type", type);
for(String value : values) {
- builder.startInnerElement("value", NamespaceURIs.JABBER_X_DATA);
+ builder.startInnerElement("value");
builder.addText(value);
builder.endInnerElement();
}
diff --git a/server/extensions/pom.xml b/server/extensions/pom.xml
index ecc6513..9c82aeb 100644
--- a/server/extensions/pom.xml
+++ b/server/extensions/pom.xml
@@ -35,6 +35,7 @@
<module>xep0045-muc</module>
<module>xep0065-socks</module>
<module>xep0124-xep0206-bosh</module>
+ <module>xep0313-mam</module>
<module>websockets</module>
</modules>
@@ -45,8 +46,9 @@
<module>xep0060-pubsub</module>
<module>xep0045-muc</module>
<module>xep0045-muc-inttest</module>
- <module>xep0124-xep0206-bosh</module>
<module>xep0065-socks</module>
+ <module>xep0124-xep0206-bosh</module>
+ <module>xep0313-mam</module>
<module>websockets</module>
</modules>
</profile>
diff --git a/server/extensions/xep0313-mam/pom.xml b/server/extensions/xep0313-mam/pom.xml
new file mode 100644
index 0000000..5046b8b
--- /dev/null
+++ b/server/extensions/xep0313-mam/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+ <!--
+ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>vysper-extensions</artifactId>
+ <groupId>org.apache.vysper</groupId>
+ <version>0.8-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.vysper.extensions</groupId>
+ <artifactId>xep0313-mam</artifactId>
+ <name>Apache Vysper XEP-0313 Message Archive Management</name>
+ <version>0.8-SNAPSHOT</version>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.vysper</groupId>
+ <artifactId>spec-compliance</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.vysper</groupId>
+ <artifactId>vysper-core</artifactId>
+ </dependency>
+
+ <!-- Runtime dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java
new file mode 100644
index 0000000..de4126e
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.vysper.event.EventListener;
+import org.apache.vysper.event.EventListenerDictionary;
+
+import java.util.Set;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMEventListenerDictionary implements EventListenerDictionary {
+
+
+
+ @Override
+ public Set<EventListener<?>> get(Class<?> eventType) {
+ return null;
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java
new file mode 100644
index 0000000..987ac85
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java
@@ -0,0 +1,103 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.vysper.event.EventListenerDictionary;
+import org.apache.vysper.event.SimpleEventListenerDictionary;
+import org.apache.vysper.xmpp.modules.DefaultDiscoAwareModule;
+import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMIQHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserMessageListener;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.Feature;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoElement;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoRequest;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.ServerInfoRequestListener;
+import org.apache.vysper.xmpp.protocol.HandlerDictionary;
+import org.apache.vysper.xmpp.protocol.NamespaceHandlerDictionary;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+
+/**
+ * A module for <a href="https://xmpp.org/extensions/xep-0313.html">XEP-0313
+ * Message Archive Management</a>
+ *
+ * @author Réda Housni Alaoui
+ */
+public class MAMModule extends DefaultDiscoAwareModule implements ServerInfoRequestListener {
+
+ private final UserMessageListener userMessageListener = new UserMessageListener();
+
+ @Override
+ public void initialize(ServerRuntimeContext serverRuntimeContext) {
+ super.initialize(serverRuntimeContext);
+
+ requireNonNull((MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class),
+ "Could not find an instance of " + MessageArchives.class);
+ }
+
+ @Override
+ public String getName() {
+ return "XEP-0313 Message Archive Management";
+ }
+
+ @Override
+ public String getVersion() {
+ return "0.6.3";
+ }
+
+ @Override
+ protected void addServerInfoRequestListeners(List<ServerInfoRequestListener> serverInfoRequestListeners) {
+ serverInfoRequestListeners.add(this);
+ }
+
+ @Override
+ public List<InfoElement> getServerInfosFor(InfoRequest request) {
+ if (StringUtils.isNotEmpty(request.getNode())) {
+ return null;
+ }
+
+ List<InfoElement> infoElements = new ArrayList<>();
+ infoElements.add(new Feature(MAMNamespaceURIs.CORE));
+ infoElements.add(new Feature(MAMNamespaceURIs.XEP0359_STANZA_IDS));
+ infoElements.add(new Feature(NamespaceURIs.JABBER_X_DATA));
+ return infoElements;
+ }
+
+ @Override
+ protected void addHandlerDictionaries(List<HandlerDictionary> dictionary) {
+ MAMIQHandler iqHandler = new MAMIQHandler();
+ dictionary.add(new NamespaceHandlerDictionary(MAMNamespaceURIs.CORE, iqHandler));
+ }
+
+ @Override
+ public Optional<EventListenerDictionary> getEventListenerDictionary() {
+ EventListenerDictionary dictionary = SimpleEventListenerDictionary.builder()
+ .register(AcceptedMessageEvent.class, userMessageListener).build();
+ return Optional.of(dictionary);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java
new file mode 100644
index 0000000..13a17a7
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMNamespaceURIs {
+
+ public static final String CORE = "urn:xmpp:mam:2";
+ public static final String XEP0359_STANZA_IDS = "urn:xmpp:sid:0";
+
+ private MAMNamespaceURIs() {
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java
new file mode 100644
index 0000000..9e3a42b
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java
@@ -0,0 +1,57 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.Message;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleMessage implements Message {
+
+ private final MessageStanza stanza;
+
+ private final ZonedDateTime dateTime;
+
+ public SimpleMessage(MessageStanza stanza) {
+ this.stanza = requireNonNull(stanza);
+ this.dateTime = ZonedDateTime.now();
+ }
+
+ @Override
+ public MessageStanza stanza() {
+ return stanza;
+ }
+
+ @Override
+ public ZonedDateTime dateTime() {
+ return dateTime;
+ }
+
+ @Override
+ public String toString() {
+ return "SimpleMessage{" + "stanza=" + stanza + ", dateTime=" + dateTime + '}';
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java
new file mode 100644
index 0000000..a50c29f
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.muc;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MUCArchiveQueryHandler implements MAMQueryHandler {
+
+ @Override
+ public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {
+ Entity queriedEntity = ofNullable(query.iqStanza().getTo()).orElse(sessionContext.getInitiatingEntity());
+ return !serverRuntimeContext.getServerEnitity().getDomain().equals(queriedEntity.getDomain());
+ }
+
+ @Override
+ public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ // MUC archives is not yet implemented
+ Stanza notImplemented = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+ query.iqStanza(), StanzaErrorType.MODIFY,
+ "Multi-User Chat message archive feature is not yet implemented", null, null);
+ return Collections.singletonList(notImplemented);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java
new file mode 100644
index 0000000..eba5826
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.pubsub;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class PubsubNodeArchiveQueryHandler implements MAMQueryHandler {
+
+ @Override
+ public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {
+ return query.getNode().isPresent();
+ }
+
+ @Override
+ public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ // PubSub node archives is not yet implemented
+ Stanza notImplemented = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+ query.iqStanza(), StanzaErrorType.CANCEL, "Pubsub node message archive feature is not yet implemented",
+ null, null);
+ return Collections.singletonList(notImplemented);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java
new file mode 100644
index 0000000..c17603e
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java
@@ -0,0 +1,79 @@
+/*
+ * 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.query;
+
+import static java.util.Objects.requireNonNull;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.EntityFilter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveEntityFilter implements EntityFilter {
+
+ private final Entity with;
+
+ private final Type type;
+
+ private final boolean ignoreResource;
+
+ public ArchiveEntityFilter(Entity archiveId, Entity with) {
+ this.with = requireNonNull(with);
+ if (archiveId.equals(with)) {
+ // If the 'with' field's value is the bare JID of the archive, the server must
+ // only return results where both
+ // the 'to' and 'from' match the bare JID (either as bare or by ignoring the
+ // resource), as otherwise every
+ // message in the archive would match
+ this.type = Type.TO_AND_FROM;
+ this.ignoreResource = true;
+ } else if (!with.isResourceSet()) {
+ // If (and only if) the supplied JID is a bare JID (i.e. no resource is
+ // present),
+ // then the server SHOULD return messages if their bare to/from address for a
+ // user archive would match it.
+ this.type = Type.TO_OR_FROM;
+ this.ignoreResource = true;
+ } else {
+ // A message in a user's archive matches if the JID matches either the to or
+ // from of the message.
+ this.type = Type.TO_OR_FROM;
+ this.ignoreResource = false;
+ }
+ }
+
+ @Override
+ public Entity entity() {
+ return with;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public boolean ignoreResource() {
+ return ignoreResource;
+ }
+
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.java
new file mode 100644
index 0000000..e0ba4c4
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.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.query;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveQuery {
+
+ private final MessageArchive archive;
+
+ private final Entity archiveId;
+
+ private final Query query;
+
+ public ArchiveQuery(MessageArchive archive, Entity archiveId, Query query) {
+ this.archive = requireNonNull(archive);
+ this.archiveId = requireNonNull(archiveId);
+ this.query = requireNonNull(query);
+ }
+
+ public ArchivedMessages execute() throws XMLSemanticError {
+ X x = query.getX();
+ QuerySet querySet = query.getSet();
+ Optional<Entity> with = query.getX().getWith();
+ if (!with.isPresent()) {
+ return archive.fetchAll(x, querySet);
+ } else {
+ return archive.fetch(new ArchiveEntityFilter(archiveId, with.get()), x, querySet);
+ }
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java
new file mode 100644
index 0000000..d0c7956
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java
@@ -0,0 +1,83 @@
+/*
+ * 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.query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.modules.core.base.handler.DefaultIQHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.muc.MUCArchiveQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.pubsub.PubsubNodeArchiveQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserArchiveQueryHandler;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMIQHandler extends DefaultIQHandler {
+
+ private final List<MAMQueryHandler> queryHandlers;
+
+ public MAMIQHandler() {
+ this(new PubsubNodeArchiveQueryHandler(), new MUCArchiveQueryHandler(), new UserArchiveQueryHandler());
+ }
+
+ public MAMIQHandler(MAMQueryHandler pubsubNodeArchiveQueryHandler, MAMQueryHandler mucArchiveQueryHandler,
+ MAMQueryHandler userArchiveQueryHandler) {
+ List<MAMQueryHandler> queryHandlers = new ArrayList<>();
+ queryHandlers.add(pubsubNodeArchiveQueryHandler);
+ queryHandlers.add(mucArchiveQueryHandler);
+ queryHandlers.add(userArchiveQueryHandler);
+ this.queryHandlers = Collections.unmodifiableList(queryHandlers);
+ }
+
+ @Override
+ protected boolean verifyInnerElement(Stanza stanza) {
+ return verifyInnerElementWorker(stanza, Query.ELEMENT_NAME)
+ && verifyInnerNamespace(stanza, MAMNamespaceURIs.CORE);
+ }
+
+ @Override
+ protected List<Stanza> handleSet(IQStanza stanza, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ Query query;
+ try {
+ query = new Query(stanza);
+ } catch (XMLSemanticError xmlSemanticError) {
+ return Collections.singletonList(ServerErrorResponses.getStanzaError(StanzaErrorCondition.NOT_ACCEPTABLE,
+ stanza, StanzaErrorType.CANCEL, null, null, null));
+ }
+
+ return queryHandlers.stream().filter(handler -> handler.supports(query, serverRuntimeContext, sessionContext))
+ .map(handler -> handler.handle(query, serverRuntimeContext, sessionContext)).findFirst()
+ .orElseGet(() -> Collections.singletonList(ServerErrorResponses.getStanzaError(
+ StanzaErrorCondition.NOT_ACCEPTABLE, stanza, StanzaErrorType.CANCEL, null, null, null)));
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMQueryHandler.java
new file mode 100644
index 0000000..61d2fab
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMQueryHandler.java
@@ -0,0 +1,38 @@
+/*
+ * 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.query;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.Stanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface MAMQueryHandler {
+
+ boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext);
+
+ List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext);
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResult.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResult.java
new file mode 100644
index 0000000..66f686c
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResult.java
@@ -0,0 +1,109 @@
+/*
+ * 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.query;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.vysper.xml.fragment.Attribute;
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xml.fragment.XMLFragment;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.datetime.DateTimeProfile;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessage;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+class MatchingArchivedMessageResult {
+
+ private static final String STANZA_ID = "stanza-id";
+
+ private final Entity initiatingEntity;
+
+ private final Entity archiveId;
+
+ private final Query query;
+
+ private final ArchivedMessage archivedMessage;
+
+ MatchingArchivedMessageResult(Entity initiatingEntity, Entity archiveId, Query query,
+ ArchivedMessage archivedMessage) {
+ this.initiatingEntity = requireNonNull(initiatingEntity);
+ this.archiveId = requireNonNull(archiveId);
+ this.query = requireNonNull(query);
+ this.archivedMessage = requireNonNull(archivedMessage);
+ }
+
+ Stanza toStanza() {
+ XMLElement result = createResult();
+ return new StanzaBuilder("message").addAttribute("to", initiatingEntity.getFullQualifiedName())
+ .addPreparedElement(result).build();
+ }
+
+ private XMLElement createResult() {
+ XMLElement forwarded = createForwarded();
+ List<Attribute> attributes = new ArrayList<>();
+ attributes.add(new Attribute("id", archivedMessage.id()));
+ query.getQueryId().map(queryId -> new Attribute("queryid", queryId)).ifPresent(attributes::add);
+ return new XMLElement(MAMNamespaceURIs.CORE, "result", null, attributes, Collections.singletonList(forwarded));
+ }
+
+ private XMLElement createForwarded() {
+ Stanza archivedStanzaWithId = completeMessageStanzaWithId();
+
+ String stamp = DateTimeProfile.getInstance().getDateTimeInUTC(archivedMessage.dateTime());
+
+ List<XMLFragment> innerElements = new ArrayList<>();
+ innerElements.add(new XMLElement(NamespaceURIs.URN_XMPP_DELAY, "delay", null,
+ Collections.singletonList(new Attribute("stamp", stamp)), Collections.emptyList()));
+ innerElements.add(archivedStanzaWithId);
+ return new XMLElement(NamespaceURIs.XEP0297_STANZA_FORWARDING, "forwarded", null, Collections.emptyList(),
+ innerElements);
+ }
+
+ private Stanza completeMessageStanzaWithId() {
+ MessageStanza archivedMessageStanza = archivedMessage.stanza();
+
+ List<XMLElement> innerElements = new ArrayList<>();
+ archivedMessageStanza.getInnerElements().stream().filter(xmlElement -> !STANZA_ID.equals(xmlElement.getName()))
+ .filter(xmlElement -> !MAMNamespaceURIs.XEP0359_STANZA_IDS.equals(xmlElement.getNamespaceURI()))
+ .forEach(innerElements::add);
+ List<Attribute> stanzaIdAttributes = new ArrayList<>();
+ stanzaIdAttributes.add(new Attribute("by", archiveId.getFullQualifiedName()));
+ stanzaIdAttributes.add(new Attribute("id", archivedMessage.id()));
+ innerElements.add(new XMLElement(MAMNamespaceURIs.XEP0359_STANZA_IDS, STANZA_ID, null, stanzaIdAttributes,
+ Collections.emptyList()));
+
+ StanzaBuilder archivedMessageStanzaWithIdBuilder = StanzaBuilder.createClone(archivedMessageStanza, false,
+ Collections.emptyList());
+ innerElements.forEach(archivedMessageStanzaWithIdBuilder::addPreparedElement);
+ return archivedMessageStanzaWithIdBuilder.build();
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResults.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResults.java
new file mode 100644
index 0000000..f78aedb
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResults.java
@@ -0,0 +1,89 @@
+/*
+ * 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.query;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.stanza.IQStanzaType;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MatchingArchivedMessageResults {
+
+ private final Entity initiatingEntity;
+
+ private final Entity archiveId;
+
+ private final Query query;
+
+ private final ArchivedMessages archivedMessages;
+
+ public MatchingArchivedMessageResults(Entity initiatingEntity, Entity archiveId, Query query,
+ ArchivedMessages archivedMessages) {
+ this.initiatingEntity = requireNonNull(initiatingEntity);
+ this.archiveId = requireNonNull(archiveId);
+ this.query = requireNonNull(query);
+ this.archivedMessages = requireNonNull(archivedMessages);
+ }
+
+ public List<Stanza> toStanzas() {
+ List<Stanza> stanzas = new ArrayList<>();
+ archivedMessages.list().stream().map(archivedMessage -> new MatchingArchivedMessageResult(initiatingEntity,
+ archiveId, query, archivedMessage)).map(MatchingArchivedMessageResult::toStanza).forEach(stanzas::add);
+ stanzas.add(buildResultIq());
+ return stanzas;
+ }
+
+ private Stanza buildResultIq() {
+ Set set = buildSet();
+ XMLElement fin = new XMLElement(MAMNamespaceURIs.CORE, "fin", null, Collections.emptyList(),
+ Collections.singletonList(set.element()));
+ return StanzaBuilder.createDirectReply(query.iqStanza(), false, IQStanzaType.RESULT).addPreparedElement(fin)
+ .build();
+ }
+
+ private Set buildSet() {
+ if (archivedMessages.isEmpty()) {
+ return Set.builder().count(archivedMessages.totalNumberOfMessages().orElse(null)).build();
+ }
+
+ List<ArchivedMessage> messagesList = archivedMessages.list();
+
+ ArchivedMessage firstMessage = messagesList.get(0);
+ ArchivedMessage lastMessage = messagesList.get(messagesList.size() - 1);
+
+ return Set.builder().startFirst().index(archivedMessages.firstMessageIndex().orElse(null))
+ .value(firstMessage.id()).endFirst().last(lastMessage.id())
+ .count(archivedMessages.totalNumberOfMessages().orElse(null)).build();
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/Query.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/Query.java
new file mode 100644
index 0000000..cddab96
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/Query.java
@@ -0,0 +1,80 @@
+/*
+ * 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.query;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class Query {
+
+ public static final String ELEMENT_NAME = "query";
+
+ private final IQStanza iqStanza;
+
+ private final XMLElement element;
+
+ public Query(IQStanza iqStanza) throws XMLSemanticError {
+ this.iqStanza = iqStanza;
+ this.element = iqStanza.getSingleInnerElementsNamed(ELEMENT_NAME);
+ if (!ELEMENT_NAME.equals(element.getName())) {
+ throw new IllegalArgumentException(
+ "Query element must be named '" + ELEMENT_NAME + "' instead of '" + element.getName() + "'");
+ }
+ if (!MAMNamespaceURIs.CORE.equals(element.getNamespaceURI())) {
+ throw new IllegalArgumentException("Query element must be bound to namespace uri '" + MAMNamespaceURIs.CORE
+ + "' instead of '" + element.getNamespaceURI() + "'");
+ }
+ }
+
+ public Optional<String> getQueryId() {
+ return ofNullable(element.getAttributeValue("queryid"));
+ }
+
+ public Optional<String> getNode() {
+ return ofNullable(element.getAttributeValue("node"));
+ }
+
+ public X getX() throws XMLSemanticError {
+ return ofNullable(element.getSingleInnerElementsNamed(X.ELEMENT_NAME)).map(X::new).orElseGet(X::empty);
+ }
+
+ public QuerySet getSet() throws XMLSemanticError {
+ XMLElement setElement = element.getSingleInnerElementsNamed(Set.ELEMENT_NAME);
+ if (setElement == null) {
+ return QuerySet.empty();
+ }
+ return new QuerySet(new Set(setElement));
+ }
+
+ public IQStanza iqStanza() {
+ return iqStanza;
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySet.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySet.java
new file mode 100644
index 0000000..8cd3024
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySet.java
@@ -0,0 +1,65 @@
+/*
+ * 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.query;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessagePageFilter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class QuerySet implements MessagePageFilter {
+
+ private final Long pageSize;
+
+ private final String firstMessageId;
+
+ private final String lastMessageId;
+
+ public QuerySet(Set set) throws XMLSemanticError {
+ this.pageSize = set.getMax().orElse(null);
+ this.firstMessageId = set.getFirst().map(Set.First::getValue).orElse(null);
+ this.lastMessageId = set.getLast().orElse(null);
+ }
+
+ public static QuerySet empty() throws XMLSemanticError {
+ return new QuerySet(Set.empty());
+ }
+
+ @Override
+ public Optional<Long> pageSize() {
+ return ofNullable(pageSize);
+ }
+
+ @Override
+ public Optional<String> firstMessageId() {
+ return ofNullable(firstMessageId);
+ }
+
+ @Override
+ public Optional<String> lastMessageId() {
+ return ofNullable(lastMessageId);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java
new file mode 100644
index 0000000..ca833c6
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java
@@ -0,0 +1,84 @@
+/*
+ * 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.query;
+
+import static java.util.Optional.ofNullable;
+
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.datetime.DateTimeProfile;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.DateTimeFilter;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.stanza.dataforms.DataFormParser;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class X implements DateTimeFilter {
+
+ public static final String ELEMENT_NAME = "x";
+
+ private final Map<String, Object> fields;
+
+ public X(XMLElement element) {
+ if (!ELEMENT_NAME.equals(element.getName())) {
+ throw new IllegalArgumentException(
+ "Query element must be named '" + ELEMENT_NAME + "' instead of '" + element.getName() + "'");
+ }
+ if (!NamespaceURIs.JABBER_X_DATA.equals(element.getNamespaceURI())) {
+ throw new IllegalArgumentException("Query element must be bound to namespace uri '" + MAMNamespaceURIs.CORE
+ + "' instead of '" + element.getNamespaceURI() + "'");
+ }
+ fields = new DataFormParser(element).extractFieldValues();
+ }
+
+ public static X empty() {
+ return new X(new XMLElement(NamespaceURIs.JABBER_X_DATA, ELEMENT_NAME, null, Collections.emptyList(),
+ Collections.emptyList()));
+ }
+
+ private Optional<String> getStringValue(String fieldName) {
+ return ofNullable(fields.get(fieldName)).map(String.class::cast);
+ }
+
+ private Optional<ZonedDateTime> getZonedDateTime(String fieldName) {
+ return getStringValue(fieldName).map(stringDate -> DateTimeProfile.getInstance().fromZonedDateTime(stringDate));
+ }
+
+ public Optional<Entity> getWith() {
+ return getStringValue("with").map(EntityImpl::parseUnchecked);
+ }
+
+ public Optional<ZonedDateTime> start() {
+ return getZonedDateTime("start");
+ }
+
+ public Optional<ZonedDateTime> end() {
+ return getZonedDateTime("end");
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java
new file mode 100644
index 0000000..3a20f30
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java
@@ -0,0 +1,34 @@
+/*
+ * 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.spi;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface ArchivedMessage extends Message {
+
+ /**
+ * @return The id of the message in its archive as defined in <a
+ * href="https://xmpp.org/extensions/xep-0313.html#archives_id">Communicating
+ * the archive ID</a>
+ */
+ String id();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java
new file mode 100644
index 0000000..1c1da2a
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java
@@ -0,0 +1,51 @@
+/*
+ * 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.spi;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface ArchivedMessages {
+
+ List<ArchivedMessage> list();
+
+ boolean isEmpty();
+
+ /**
+ * This integer specifies the position within the full set (which MAY be
+ * approximate) of the first message in the page. If that message is the first
+ * in the full set, then the index SHOULD be '0'. If the last message in the
+ * page is the last message in the full set, then the value SHOULD be the
+ * specified count minus the number of messages in the last page.
+ */
+ Optional<Long> firstMessageIndex();
+
+ /**
+ * @return The total number of messages that could be retrieved by paging
+ * through the pages.
+ */
+ Optional<Long> totalNumberOfMessages();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/DateTimeFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/DateTimeFilter.java
new file mode 100644
index 0000000..0a033d8
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/DateTimeFilter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.spi;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+/**
+ * <a href="https://xmpp.org/extensions/xep-0313.html#filter-time">Filtering by
+ * time received</a>
+ *
+ * @author Réda Housni Alaoui
+ */
+public interface DateTimeFilter {
+
+ /**
+ * The 'start' field is used to filter out messages before a certain date/time.
+ * If specified, a server MUST only return messages whose timestamp is equal to
+ * or later than the given timestamp.
+ */
+ Optional<ZonedDateTime> start();
+
+ /**
+ * The 'end' field is used to exclude from the results messages
+ * after a certain point in time. If specified, a server MUST only return
+ * messages whose timestamp is equal to or earlier than the timestamp given in
+ * the 'end' field.
+ */
+ Optional<ZonedDateTime> end();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java
new file mode 100644
index 0000000..f274c11
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.spi;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface EntityFilter {
+
+ enum Type {
+ /**
+ * The message 'to' or 'from' must match the provided entity
+ */
+ TO_OR_FROM,
+ /**
+ * The message 'to' and 'from' must match the provided entity
+ */
+ TO_AND_FROM;
+ }
+
+ /**
+ * @return The entity to filter by
+ */
+ Entity entity();
+
+ /**
+ * @return The type of filtering
+ */
+ Type type();
+
+ /**
+ * @return True to ignore the filtering entity, the message 'to' and the message
+ * 'from' <b>resource part</b>.
+ */
+ boolean ignoreResource();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java
new file mode 100644
index 0000000..0ef98e5
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java
@@ -0,0 +1,35 @@
+/*
+ * 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.spi;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface Message {
+
+ MessageStanza stanza();
+
+ ZonedDateTime dateTime();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java
new file mode 100644
index 0000000..f8c56ce
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java
@@ -0,0 +1,43 @@
+/*
+ * 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.spi;
+
+/**
+ * <a href=
+ * "https://xmpp.org/extensions/xep-0313.html#business-storeret">Storage and
+ * Retrieval Rules</a>
+ *
+ * @author Réda Housni Alaoui
+ */
+public interface MessageArchive {
+
+ /**
+ * At a minimum, the server MUST store the <body> elements of a stanza. It is
+ * suggested that other elements that are used in a given deployment to
+ * supplement conversations (e.g. XHTML-IM payloads) are also stored. Other
+ * elements MAY be stored.
+ */
+ void archive(Message message);
+
+ ArchivedMessages fetchAll(DateTimeFilter dateTimeFilter, MessagePageFilter pageFilter);
+
+ ArchivedMessages fetch(EntityFilter entityFilter, DateTimeFilter dateTimeFilter, MessagePageFilter pageFilter);
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java
new file mode 100644
index 0000000..e4df43b
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java
@@ -0,0 +1,34 @@
+/*
+ * 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.spi;
+
+import java.util.Optional;
+
+import org.apache.vysper.storage.StorageProvider;
+import org.apache.vysper.xmpp.addressing.Entity;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface MessageArchives extends StorageProvider {
+
+ Optional<MessageArchive> retrieveUserMessageArchive(Entity userBareJid);
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java
new file mode 100644
index 0000000..76cc182
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.spi;
+
+import java.util.Optional;
+import java.util.OptionalLong;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface MessagePageFilter {
+
+ Optional<Long> pageSize();
+
+ Optional<String> firstMessageId();
+
+ Optional<String> lastMessageId();
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java
new file mode 100644
index 0000000..c884b98
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java
@@ -0,0 +1,60 @@
+/*
+ * 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.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleArchivedMessage implements ArchivedMessage {
+
+ private final String id;
+
+ private final ZonedDateTime dateTime;
+
+ private final MessageStanza stanza;
+
+ public SimpleArchivedMessage(String id, ZonedDateTime dateTime, MessageStanza stanza) {
+ this.id = requireNonNull(id);
+ this.dateTime = requireNonNull(dateTime);
+ this.stanza = requireNonNull(stanza);
+ }
+
+ @Override
+ public MessageStanza stanza() {
+ return stanza;
+ }
+
+ @Override
+ public ZonedDateTime dateTime() {
+ return dateTime;
+ }
+
+ @Override
+ public String id() {
+ return id;
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java
new file mode 100644
index 0000000..8fe90cf
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java
@@ -0,0 +1,74 @@
+/*
+ * 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.spi;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleArchivedMessages implements ArchivedMessages {
+
+ private final List<ArchivedMessage> list;
+
+ private final Long firstMessageIndex;
+
+ private final Long totalNumberOfMessages;
+
+ public SimpleArchivedMessages(List<ArchivedMessage> list) {
+ this(list, null, null);
+ }
+
+ public SimpleArchivedMessages(List<ArchivedMessage> list, Long firstMessageIndex) {
+ this(list, firstMessageIndex, null);
+ }
+
+ public SimpleArchivedMessages(List<ArchivedMessage> list, Long firstMessageIndex, Long totalNumberOfMessages) {
+ this.list = new ArrayList<>(list);
+ this.firstMessageIndex = firstMessageIndex;
+ this.totalNumberOfMessages = totalNumberOfMessages;
+ }
+
+ @Override
+ public List<ArchivedMessage> list() {
+ return new ArrayList<>(list);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ @Override
+ public Optional<Long> firstMessageIndex() {
+ return ofNullable(firstMessageIndex);
+ }
+
+ @Override
+ public Optional<Long> totalNumberOfMessages() {
+ return ofNullable(totalNumberOfMessages);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java
new file mode 100644
index 0000000..32b3556
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java
@@ -0,0 +1,93 @@
+/*
+ * 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.user;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.ArchiveQuery;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MatchingArchivedMessageResults;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class UserArchiveQueryHandler implements MAMQueryHandler {
+
+ @Override
+ public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {
+ return true;
+ }
+
+ @Override
+ public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) {
+ try {
+ return doHandle(query, serverRuntimeContext, sessionContext);
+ } catch (XMLSemanticError xmlSemanticError) {
+ Stanza internalServerError = ServerErrorResponses.getStanzaError(StanzaErrorCondition.INTERNAL_SERVER_ERROR,
+ query.iqStanza(), StanzaErrorType.CANCEL, null, null, null);
+ return Collections.singletonList(internalServerError);
+ }
+ }
+
+ private List<Stanza> doHandle(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext)
+ throws XMLSemanticError {
+ Entity initiatingEntity = sessionContext.getInitiatingEntity();
+ Entity archiveOwner = ofNullable(query.iqStanza().getTo()).orElse(initiatingEntity);
+ if (!sessionContext.getInitiatingEntity().getBareJID().equals(archiveOwner.getBareJID())) {
+ // The initiating user is trying to read the archive of another user
+ return Collections.singletonList(ServerErrorResponses.getStanzaError(StanzaErrorCondition.FORBIDDEN,
+ query.iqStanza(), StanzaErrorType.CANCEL,
+ "Entity " + initiatingEntity + " is not allowed to query " + archiveOwner + " message archives.",
+ null, null));
+ }
+
+ MessageArchives archives = requireNonNull(
+ (MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class),
+ "Could not find an instance of " + MessageArchives.class);
+
+ Entity archiveId = archiveOwner.getBareJID();
+ Optional<MessageArchive> archive = archives.retrieveUserMessageArchive(archiveId);
+ if (!archive.isPresent()) {
+ return Collections.singletonList(ServerErrorResponses.getStanzaError(StanzaErrorCondition.ITEM_NOT_FOUND,
+ query.iqStanza(), StanzaErrorType.CANCEL, "No user message archive found for entity " + archiveId,
+ null, null));
+ }
+
+ ArchivedMessages archivedMessages = new ArchiveQuery(archive.get(), archiveId, query).execute();
+ return new MatchingArchivedMessageResults(initiatingEntity, archiveId, query, archivedMessages).toStanzas();
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java
new file mode 100644
index 0000000..9dc1fbd
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java
@@ -0,0 +1,81 @@
+/*
+ * 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.user;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import org.apache.vysper.event.EventListener;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent;
+import org.apache.vysper.xmpp.modules.core.base.handler.XMPPCoreStanzaHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SimpleMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class UserMessageListener implements EventListener<AcceptedMessageEvent> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UserMessageListener.class);
+
+ @Override
+ public void onEvent(AcceptedMessageEvent acceptedMessage) {
+ archive(acceptedMessage);
+ }
+
+ private void archive(AcceptedMessageEvent acceptedMessage) {
+ ServerRuntimeContext serverRuntimeContext = acceptedMessage.serverRuntimeContext();
+ MessageArchives archives = requireNonNull(
+ (MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class),
+ "Could not find an instance of " + MessageArchives.class);
+
+ MessageStanza messageStanza = acceptedMessage.messageStanza();
+
+ Entity archiveJID;
+ if (acceptedMessage.isOutbound()) {
+ // We will store the message in the sender archive
+ SessionContext sessionContext = acceptedMessage.sessionContext();
+ archiveJID = XMPPCoreStanzaHandler.extractSenderJID(messageStanza, sessionContext);
+ } else {
+ // We will store the message in the receiver archive
+ archiveJID = requireNonNull(messageStanza.getTo(), "No 'to' found in " + messageStanza);
+ }
+
+ // 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();
+
+ Optional<MessageArchive> userArchive = archives.retrieveUserMessageArchive(archiveJID);
+ if (!userArchive.isPresent()) {
+ LOG.debug("No archive returned for user with bare JID '{}'", archiveJID);
+ return;
+ }
+
+ userArchive.get().archive(new SimpleMessage(messageStanza));
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java
new file mode 100644
index 0000000..4de303a
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.apache.vysper.xmpp.modules.servicediscovery.management.Feature;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoElement;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoRequest;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.ServerInfoRequestListener;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.ServiceDiscoveryRequestException;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+/**
+ * @author The Apache MINA Project (dev@mina.apache.org)
+ */
+public class MAMModuleTest {
+
+ private MAMModule tested = new MAMModule();
+
+ @Test
+ public void nameMustBeProvided() {
+ Assert.assertNotNull(tested.getName());
+ }
+
+ @Test
+ public void versionMustBeProvided() {
+ Assert.assertNotNull(tested.getVersion());
+ }
+
+ @Test
+ public void getServerInfosFor() throws ServiceDiscoveryRequestException {
+ List<ServerInfoRequestListener> serverInfoRequestListeners = new ArrayList<>();
+
+ tested.addServerInfoRequestListeners(serverInfoRequestListeners);
+
+ Assert.assertEquals(1, serverInfoRequestListeners.size());
+
+ List<InfoElement> infoElements = serverInfoRequestListeners.get(0)
+ .getServerInfosFor(new InfoRequest(null, null, null, null));
+
+ Assert.assertEquals(3, infoElements.size());
+ Assert.assertTrue(infoElements.get(0) instanceof Feature);
+ Assert.assertTrue(infoElements.get(1) instanceof Feature);
+ Assert.assertTrue(infoElements.get(2) instanceof Feature);
+ Assert.assertEquals(MAMNamespaceURIs.CORE, ((Feature) infoElements.get(0)).getVar());
+ Assert.assertEquals(MAMNamespaceURIs.XEP0359_STANZA_IDS, ((Feature) infoElements.get(1)).getVar());
+ Assert.assertEquals(NamespaceURIs.JABBER_X_DATA, ((Feature) infoElements.get(2)).getVar());
+ }
+
+ @Test
+ public void getServerInfosForWithNode() throws ServiceDiscoveryRequestException {
+ List<ServerInfoRequestListener> serverInfoRequestListeners = new ArrayList<>();
+ tested.addServerInfoRequestListeners(serverInfoRequestListeners);
+ Assert.assertEquals(1, serverInfoRequestListeners.size());
+
+ Assert.assertNull(
+ serverInfoRequestListeners.get(0).getServerInfosFor(new InfoRequest(null, null, "node", null)));
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..61ba7b8
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
@@ -0,0 +1,166 @@
+/*
+ * 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 static org.mockito.Mockito.mock;
+
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.vysper.event.EventBus;
+import org.apache.vysper.storage.StorageProvider;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.authentication.UserAuthentication;
+import org.apache.vysper.xmpp.delivery.StanzaRelay;
+import org.apache.vysper.xmpp.modules.Module;
+import org.apache.vysper.xmpp.modules.ServerRuntimeContextService;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchivesMock;
+import org.apache.vysper.xmpp.protocol.StanzaHandler;
+import org.apache.vysper.xmpp.protocol.StanzaProcessor;
+import org.apache.vysper.xmpp.server.ServerFeatures;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.components.Component;
+import org.apache.vysper.xmpp.server.s2s.XMPPServerConnectorRegistry;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.state.presence.LatestPresenceCache;
+import org.apache.vysper.xmpp.state.resourcebinding.ResourceRegistry;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ServerRuntimeContextMock implements ServerRuntimeContext {
+
+ private MessageArchivesMock userMessageArchives;
+
+ public MessageArchivesMock givenUserMessageArchives() {
+ userMessageArchives = new MessageArchivesMock();
+ return userMessageArchives;
+ }
+
+ @Override
+ public StanzaHandler getHandler(Stanza stanza) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getNextSessionId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Entity getServerEnitity() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDefaultXMLLang() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StanzaProcessor getStanzaProcessor() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StanzaRelay getStanzaRelay() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServerFeatures getServerFeatures() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SSLContext getSslContext() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UserAuthentication getUserAuthentication() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResourceRegistry getResourceRegistry() {
+ return mock(ResourceRegistry.class);
+ }
+
+ @Override
+ public LatestPresenceCache getPresenceCache() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerServerRuntimeContextService(ServerRuntimeContextService service) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServerRuntimeContextService getServerRuntimeContextService(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StorageProvider getStorageProvider(Class<? extends StorageProvider> clazz) {
+ if (MessageArchives.class.equals(clazz)) {
+ return userMessageArchives;
+ }
+ return null;
+ }
+
+ @Override
+ public void registerComponent(Component component) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StanzaProcessor getComponentStanzaProcessor(Entity entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public XMPPServerConnectorRegistry getServerConnectorRegistry() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Module> getModules() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T getModule(Class<T> clazz) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addModule(Module module) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EventBus getEventBus() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java
new file mode 100644
index 0000000..5b69042
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java
@@ -0,0 +1,145 @@
+/*
+ * 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 org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.SessionState;
+import org.apache.vysper.xmpp.state.resourcebinding.BindException;
+import org.apache.vysper.xmpp.writer.StanzaWriter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SessionContextMock implements SessionContext {
+
+ private ServerRuntimeContext serverRuntimeContext;
+
+ private Entity initiatingEntity;
+
+ public void givenServerRuntimeContext(ServerRuntimeContext serverRuntimeContext) {
+ this.serverRuntimeContext = serverRuntimeContext;
+ }
+
+ public void givenInitiatingEntity(Entity initiatingEntity) {
+ this.initiatingEntity = initiatingEntity;
+ }
+
+ @Override
+ public ServerRuntimeContext getServerRuntimeContext() {
+ return serverRuntimeContext;
+ }
+
+ @Override
+ public boolean isRemotelyInitiatedSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Entity getInitiatingEntity() {
+ return initiatingEntity;
+ }
+
+ @Override
+ public void setInitiatingEntity(Entity entity) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isServerToServer() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setServerToServer() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setClientToServer() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SessionState getState() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSessionId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getXMLLang() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setXMLLang(String languageCode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StanzaWriter getResponseWriter() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void endSession(SessionTerminationCause terminationCause) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Entity getServerJID() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void switchToTLS(boolean delayed, boolean clientTls) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setIsReopeningXMLStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String bindResource() throws BindException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String nextSequenceValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object putAttribute(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java
new file mode 100644
index 0000000..bbf9369
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.apache.vysper.xml.fragment.Renderer;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.junit.Assert;
+
+public class StanzaAssert {
+
+ public static void assertEquals(Stanza expected, Stanza actual) {
+ try {
+ Assert.assertEquals(expected, actual);
+ } catch(Throwable e) {
+ // print something useful
+ Assert.assertEquals(new Renderer(expected).getComplete(), new Renderer(actual).getComplete());
+ }
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilterTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilterTest.java
new file mode 100644
index 0000000..913b23c
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilterTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.EntityFilter;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveEntityFilterTest {
+
+ @Test
+ public void withMatchingArchiveId() {
+ Entity with = EntityImpl.parseUnchecked("juliet@foo.com");
+ ArchiveEntityFilter tested = new ArchiveEntityFilter(with, with);
+
+ assertEquals(with, tested.entity());
+ assertEquals(EntityFilter.Type.TO_AND_FROM, tested.type());
+ assertTrue(tested.ignoreResource());
+ }
+
+ @Test
+ public void withBeingBareJid() {
+ Entity archiveId = EntityImpl.parseUnchecked("romeo@foo.com");
+ Entity with = EntityImpl.parseUnchecked("juliet@foo.com");
+ ArchiveEntityFilter tested = new ArchiveEntityFilter(archiveId, with);
+
+ assertEquals(with, tested.entity());
+ assertEquals(EntityFilter.Type.TO_OR_FROM, tested.type());
+ assertTrue(tested.ignoreResource());
+ }
+
+ @Test
+ public void withNotMatchingArchiveIdAndNotBeingBareJid() {
+ Entity archiveId = EntityImpl.parseUnchecked("romeo@foo.com");
+ Entity with = EntityImpl.parseUnchecked("juliet@foo.com/balcony");
+ ArchiveEntityFilter tested = new ArchiveEntityFilter(archiveId, with);
+
+ assertEquals(with, tested.entity());
+ assertEquals(EntityFilter.Type.TO_OR_FROM, tested.type());
+ assertFalse(tested.ignoreResource());
+ }
+
+}
\ No newline at end of file
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQueryTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQueryTest.java
new file mode 100644
index 0000000..aad804f
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQueryTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveQueryTest {
+
+ private static final Entity JULIET = EntityImpl.parseUnchecked("juliet@foo.com");
+
+ private static final Entity ARCHIVE_ID = JULIET;
+
+ private MessageArchive archive;
+
+ private X x;
+
+ private ArchiveQuery tested;
+
+ @Before
+ public void before() throws XMLSemanticError {
+ archive = mock(MessageArchive.class);
+ Query query = mock(Query.class);
+ x = mock(X.class);
+ when(query.getX()).thenReturn(x);
+ tested = new ArchiveQuery(archive, ARCHIVE_ID, query);
+ }
+
+ @Test
+ public void executeWithoutWith() throws XMLSemanticError {
+ when(x.getWith()).thenReturn(Optional.empty());
+
+ ArchivedMessages archivedMessages = mock(ArchivedMessages.class);
+ when(archive.fetchAll(any(), any())).thenReturn(archivedMessages);
+
+ assertEquals(archivedMessages, tested.execute());
+ }
+
+ @Test
+ public void executeWithWith() throws XMLSemanticError {
+ when(x.getWith()).thenReturn(Optional.of(JULIET));
+
+ ArchivedMessages archivedMessages = mock(ArchivedMessages.class);
+ when(archive.fetch(any(), any(), any())).thenReturn(archivedMessages);
+
+ assertEquals(archivedMessages, tested.execute());
+ }
+
+}
\ No newline at end of file
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandlerTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandlerTest.java
new file mode 100644
index 0000000..fb5fdad
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandlerTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.query;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.muc.MUCArchiveQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserArchiveQueryHandler;
+import org.apache.vysper.xmpp.parser.XMLParserUtil;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMIQHandlerTest {
+
+ private MAMQueryHandler pubsubNodeArchiveQueryHandler;
+
+ private MAMQueryHandler mucArchiveQueryHandler;
+
+ private MAMQueryHandler userArchiveQueryHandler;
+
+ private IQStanza stanza;
+
+ private ServerRuntimeContext serverRuntimeContext;
+
+ private SessionContext sessionContext;
+
+ private MAMIQHandler tested;
+
+ @Before
+ public void before() throws IOException, SAXException {
+ pubsubNodeArchiveQueryHandler = mock(MAMQueryHandler.class);
+ mucArchiveQueryHandler = mock(MUCArchiveQueryHandler.class);
+ userArchiveQueryHandler = mock(UserArchiveQueryHandler.class);
+
+ tested = new MAMIQHandler(pubsubNodeArchiveQueryHandler, mucArchiveQueryHandler, userArchiveQueryHandler);
+
+ XMLElement queryIqElement = XMLParserUtil.parseRequiredDocument(
+ "<iq type='set' id='juliet1'><query xmlns='urn:xmpp:mam:2' queryid='f27'/></iq>");
+ stanza = new IQStanza(StanzaBuilder.createClone(queryIqElement, true, Collections.emptyList()).build());
+ serverRuntimeContext = mock(ServerRuntimeContext.class);
+ sessionContext = mock(SessionContext.class);
+ }
+
+ @Test
+ public void verifySupportStanza() {
+ assertTrue(tested.verifyInnerElement(stanza));
+ }
+
+ @Test
+ public void verifyUnsupportStanzaNamespace() throws IOException, SAXException {
+ XMLElement queryIqElement = XMLParserUtil
+ .parseRequiredDocument("<iq type='set' id='juliet1'><query queryid='f27'/></iq>");
+ Stanza stanza = StanzaBuilder.createClone(queryIqElement, true, Collections.emptyList()).build();
+
+ assertFalse(tested.verifyInnerElement(stanza));
+ }
+
+ @Test
+ public void verifyUnsupportStanzaName() throws IOException, SAXException {
+ XMLElement queryIqElement = XMLParserUtil
+ .parseRequiredDocument("<iq type='set' id='juliet1'><foo xmlns='urn:xmpp:mam:2' queryid='f27'/></iq>");
+ Stanza stanza = StanzaBuilder.createClone(queryIqElement, true, Collections.emptyList()).build();
+
+ assertFalse(tested.verifyInnerElement(stanza));
+ }
+
+ @Test
+ public void pubsubHasHighestPriority() {
+ when(pubsubNodeArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+ when(mucArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+ when(userArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+
+ tested.handleSet(stanza, serverRuntimeContext, sessionContext);
+ verify(pubsubNodeArchiveQueryHandler).handle(any(), any(), any());
+ verify(mucArchiveQueryHandler, never()).handle(any(), any(), any());
+ verify(userArchiveQueryHandler, never()).handle(any(), any(), any());
+ }
+
+ @Test
+ public void mucHasLessPriorityThanPubsub() {
+ when(pubsubNodeArchiveQueryHandler.supports(any(), any(), any())).thenReturn(false);
+ when(mucArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+ when(userArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+
+ tested.handleSet(stanza, serverRuntimeContext, sessionContext);
+ verify(pubsubNodeArchiveQueryHandler, never()).handle(any(), any(), any());
+ verify(mucArchiveQueryHandler).handle(any(), any(), any());
+ verify(userArchiveQueryHandler, never()).handle(any(), any(), any());
+ }
+
+ @Test
+ public void userHasLowestPriority() {
+ when(pubsubNodeArchiveQueryHandler.supports(any(), any(), any())).thenReturn(false);
+ when(mucArchiveQueryHandler.supports(any(), any(), any())).thenReturn(false);
+ when(userArchiveQueryHandler.supports(any(), any(), any())).thenReturn(true);
+
+ tested.handleSet(stanza, serverRuntimeContext, sessionContext);
+ verify(pubsubNodeArchiveQueryHandler, never()).handle(any(), any(), any());
+ verify(mucArchiveQueryHandler, never()).handle(any(), any(), any());
+ verify(userArchiveQueryHandler).handle(any(), any(), any());
+ }
+
+}
\ No newline at end of file
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResultsTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResultsTest.java
new file mode 100644
index 0000000..2a14c05
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResultsTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.query;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+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.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.StanzaAssert;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.SimpleArchivedMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.SimpleArchivedMessages;
+import org.apache.vysper.xmpp.parser.XMLParserUtil;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MatchingArchivedMessageResultsTest {
+
+ private static final Entity INITIATING_ENTITY = EntityImpl.parseUnchecked("juliet@capulet.lit/chamber");
+
+ private static final Entity ARCHIVE_ID = EntityImpl.parseUnchecked("juliet@capulet.lit");
+
+ private Query query;
+
+ private MessageStanza messageStanza;
+
+ @Before
+ public void before() throws XMLSemanticError, IOException, SAXException {
+ XMLElement queryIqElement = XMLParserUtil.parseRequiredDocument(
+ "<iq type='set' id='juliet1'><query xmlns='urn:xmpp:mam:2' queryid='f27'/></iq>");
+ query = new Query(
+ new IQStanza(StanzaBuilder.createClone(queryIqElement, true, Collections.emptyList()).build()));
+
+ XMLElement messageElement = XMLParserUtil.parseRequiredDocument(
+ "<message xmlns='jabber:client' from=\"witch@shakespeare.lit\" to=\"macbeth@shakespeare.lit\">"
+ + "<body>Hail to thee</body></message>");
+ messageStanza = new MessageStanza(
+ StanzaBuilder.createClone(messageElement, true, Collections.emptyList()).build());
+ }
+
+ @Test
+ public void testHappyPath() throws IOException, SAXException {
+ SimpleArchivedMessage archivedMessage1 = new SimpleArchivedMessage("28482-98726-73623",
+ ZonedDateTime.of(LocalDateTime.of(2010, 7, 10, 23, 8, 25), ZoneId.of("Z")), messageStanza);
+ SimpleArchivedMessage archivedMessage2 = new SimpleArchivedMessage("09af3-cc343-b409f",
+ ZonedDateTime.of(LocalDateTime.of(2010, 7, 10, 23, 8, 25), ZoneId.of("Z")), messageStanza);
+
+ List<ArchivedMessage> archivedMessagesList = new ArrayList<>();
+ archivedMessagesList.add(archivedMessage1);
+ archivedMessagesList.add(archivedMessage2);
+
+ SimpleArchivedMessages archivedMessages = new SimpleArchivedMessages(archivedMessagesList, 0L, 2L);
+
+ MatchingArchivedMessageResults tested = new MatchingArchivedMessageResults(INITIATING_ENTITY, ARCHIVE_ID, query,
+ archivedMessages);
+
+ List<Stanza> responseStanzas = tested.toStanzas();
+ assertEquals(3, responseStanzas.size());
+
+ StanzaAssert.assertEquals(StanzaBuilder
+ .createClone(XMLParserUtil.parseRequiredDocument("<message to='juliet@capulet.lit/chamber'>"
+ + " <result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>"
+ + " <forwarded xmlns='urn:xmpp:forward:0'>"
+ + " <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>"
+ + " <message xmlns='jabber:client' from='witch@shakespeare.lit' to='macbeth@shakespeare.lit'>"
+ + " <body>Hail to thee</body>"
+ + " <stanza-id xmlns='urn:xmpp:sid:0' by='juliet@capulet.lit' id='28482-98726-73623'/>"
+ + "</message></forwarded></result></message>"), true, null)
+ .build(), responseStanzas.get(0));
+
+ StanzaAssert.assertEquals(StanzaBuilder
+ .createClone(XMLParserUtil.parseRequiredDocument("<message to='juliet@capulet.lit/chamber'>"
+ + " <result xmlns='urn:xmpp:mam:2' queryid='f27' id='09af3-cc343-b409f'>"
+ + " <forwarded xmlns='urn:xmpp:forward:0'>"
+ + " <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>"
+ + " <message xmlns='jabber:client' from='witch@shakespeare.lit' to='macbeth@shakespeare.lit'>"
+ + " <body>Hail to thee</body>"
+ + " <stanza-id xmlns='urn:xmpp:sid:0' by='juliet@capulet.lit' id='09af3-cc343-b409f'/>"
+ + "</message></forwarded></result></message>"), true, null)
+ .build(), responseStanzas.get(1));
+
+ StanzaAssert.assertEquals(
+ StanzaBuilder.createClone(XMLParserUtil
+ .parseRequiredDocument("<iq type='result' id='juliet1'><fin xmlns='urn:xmpp:mam:2'>"
+ + " <set xmlns='http://jabber.org/protocol/rsm'>"
+ + " <count>2</count><first index='0'>28482-98726-73623</first>"
+ + " <last>09af3-cc343-b409f</last></set></fin></iq>"),
+ true, null).build(),
+ responseStanzas.get(2));
+ }
+
+ @Test
+ public void testEmptyPage() throws IOException, SAXException {
+ SimpleArchivedMessages archivedMessages = new SimpleArchivedMessages(Collections.emptyList(), 0L, 50L);
+
+ MatchingArchivedMessageResults tested = new MatchingArchivedMessageResults(INITIATING_ENTITY, ARCHIVE_ID, query,
+ archivedMessages);
+
+ List<Stanza> responseStanzas = tested.toStanzas();
+ assertEquals(1, responseStanzas.size());
+
+ StanzaAssert.assertEquals(
+ StanzaBuilder.createClone(XMLParserUtil
+ .parseRequiredDocument("<iq type='result' id='juliet1'><fin xmlns='urn:xmpp:mam:2'>"
+ + " <set xmlns='http://jabber.org/protocol/rsm'>"
+ + " <count>50</count></set></fin></iq>"),
+ true, null).build(),
+ responseStanzas.get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySetTest.java
similarity index 53%
copy from server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
copy to server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySetTest.java
index c52dfd6..c57ebf2 100644
--- a/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySetTest.java
@@ -17,33 +17,26 @@
* under the License.
*
*/
-package org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management;
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.query;
import static org.junit.Assert.assertEquals;
import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
import org.junit.Test;
/**
* @author Réda Housni Alaoui
*/
-public class ResultSetManagementTest {
+public class QuerySetTest {
@Test
- public void parse() throws XMLSemanticError {
- Set tested = Set.builder().after("after-value").before("before-value").count(5)
- .index(10).last("last-value").max(15).startFirst().index(20).value("first-value").endFirst().build();
-
- assertEquals("after-value", tested.getAfter().get());
- assertEquals("before-value", tested.getBefore().get());
- assertEquals(5, tested.getCount().getAsInt());
- assertEquals(10, tested.getIndex().getAsInt());
- assertEquals("last-value", tested.getLast().get());
- assertEquals(15, tested.getMax().getAsInt());
-
- Set.First first = tested.getFirst().get();
- assertEquals("first-value", first.getValue());
- assertEquals(20, first.getIndex().getAsInt());
+ public void test() throws XMLSemanticError {
+ Set set = Set.builder().max(10L).startFirst().value("first").endFirst().last("last").build();
+ QuerySet tested = new QuerySet(set);
+ assertEquals(10L, (long) tested.pageSize().orElse(0L));
+ assertEquals("first", tested.firstMessageId().orElse(null));
+ assertEquals("last", tested.lastMessageId().orElse(null));
}
}
\ No newline at end of file
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java
new file mode 100644
index 0000000..a9387b8
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java
@@ -0,0 +1,61 @@
+/*
+ * 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.spi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MessageArchiveMock implements MessageArchive {
+
+ private final Queue<Message> messages = new LinkedList<>();
+
+ @Override
+ public void archive(Message message) {
+ messages.add(message);
+ }
+
+ @Override
+ public ArchivedMessages fetchAll(DateTimeFilter dateTimeRange, MessagePageFilter pageRequest) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArchivedMessages fetch(EntityFilter entityFilter, DateTimeFilter dateTimeFilter,
+ MessagePageFilter pageFilter) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void assertUniqueArchivedMessageStanza(MessageStanza messageStanza) {
+ assertEquals(1, messages.size());
+ assertEquals(messageStanza, messages.peek().stanza());
+ }
+
+ public void assertEmpty() {
+ assertTrue(messages.isEmpty());
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchivesMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchivesMock.java
new file mode 100644
index 0000000..b484fe9
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchivesMock.java
@@ -0,0 +1,45 @@
+/*
+ * 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.spi;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MessageArchivesMock implements MessageArchives {
+
+ private final Map<Entity, MessageArchive> archiveById = new HashMap<>();
+
+ public MessageArchiveMock givenArchive(Entity archiveId) {
+ MessageArchiveMock userMessageArchiveMock = new MessageArchiveMock();
+ archiveById.put(archiveId, userMessageArchiveMock);
+ return userMessageArchiveMock;
+ }
+
+ @Override
+ public Optional<MessageArchive> retrieveUserMessageArchive(Entity userBareJid) {
+ return Optional.ofNullable(archiveById.get(userBareJid));
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java
new file mode 100644
index 0000000..beebdaa
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.user;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.ServerRuntimeContextMock;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SessionContextMock;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchiveMock;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchivesMock;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class UserMessageListenerTest {
+
+ 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 MACBETH_IN_KITCHEN = EntityImpl.parseUnchecked("macbeth@shakespeare.lit/kitchen");
+
+ private static final Entity ALICE_IN_RABBIT_HOLE = EntityImpl.parseUnchecked("alice@carol.lit/rabbit-hole");
+
+ private static final Entity INITIATING_ENTITY = JULIET_IN_CHAMBER;
+
+ private MessageArchiveMock julietArchive;
+
+ private MessageArchiveMock romeoArchive;
+
+ private MessageArchiveMock macbethArchive;
+
+ private AcceptedMessageEvent acceptedMessage;
+
+ private MessageStanza messageStanza;
+
+ private UserMessageListener tested;
+
+ @Before
+ public void before() {
+ ServerRuntimeContextMock serverRuntimeContext = new ServerRuntimeContextMock();
+
+ MessageArchivesMock archives = serverRuntimeContext.givenUserMessageArchives();
+
+ julietArchive = archives.givenArchive(JULIET_IN_CHAMBER.getBareJID());
+ romeoArchive = archives.givenArchive(ROMEO_IN_ORCHARD.getBareJID());
+ macbethArchive = archives.givenArchive(MACBETH_IN_KITCHEN.getBareJID());
+
+ SessionContextMock sessionContext = new SessionContextMock();
+ sessionContext.givenInitiatingEntity(INITIATING_ENTITY);
+ sessionContext.givenServerRuntimeContext(serverRuntimeContext);
+
+ messageStanza = mock(MessageStanza.class);
+ acceptedMessage = mock(AcceptedMessageEvent.class);
+ when(acceptedMessage.serverRuntimeContext()).thenReturn(serverRuntimeContext);
+ when(acceptedMessage.sessionContext()).thenReturn(sessionContext);
+ when(acceptedMessage.messageStanza()).thenReturn(messageStanza);
+
+ tested = new UserMessageListener();
+ }
+
+ @Test
+ public void outboundMessageHavingFrom() {
+ when(acceptedMessage.isOutbound()).thenReturn(true);
+ when(messageStanza.getFrom()).thenReturn(ROMEO_IN_ORCHARD);
+
+ tested.onEvent(acceptedMessage);
+
+ romeoArchive.assertUniqueArchivedMessageStanza(messageStanza);
+ }
+
+ @Test
+ public void outboundMessageWithoutFrom() {
+ when(acceptedMessage.isOutbound()).thenReturn(true);
+
+ tested.onEvent(acceptedMessage);
+
+ julietArchive.assertUniqueArchivedMessageStanza(messageStanza);
+ }
+
+ @Test
+ public void inboundMessage() {
+ when(acceptedMessage.isOutbound()).thenReturn(false);
+ when(messageStanza.getTo()).thenReturn(MACBETH_IN_KITCHEN);
+
+ tested.onEvent(acceptedMessage);
+
+ macbethArchive.assertUniqueArchivedMessageStanza(messageStanza);
+ }
+
+ @Test
+ public void unexistingArchive() {
+ when(acceptedMessage.isOutbound()).thenReturn(true);
+ when(messageStanza.getFrom()).thenReturn(ALICE_IN_RABBIT_HOLE);
+
+ tested.onEvent(acceptedMessage);
+
+ julietArchive.assertEmpty();
+ romeoArchive.assertEmpty();
+ macbethArchive.assertEmpty();
+ }
+
+}
\ No newline at end of file