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/17 16:35:42 UTC
[mina-vysper] branch xep-0313 updated (5df21b9 -> 315535d)
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 5df21b9 XEP-0313 Message Archive Management
new 315535d 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 (5df21b9)
\
N -- N -- N refs/heads/xep-0313 (315535d)
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:
.../core/base/handler/AcceptedMessageEvent.java | 10 +--
.../modules/core/base/handler/MessageHandler.java | 6 +-
.../archiving/user/UserMessageListener.java | 3 +-
.../extension/xep0313_mam/query/MAMIQHandler.java | 42 ++++++-------
...ageUnpagedRequest.java => MAMQueryHandler.java} | 24 +++-----
.../xep0313_mam/query/MUCArchiveQueryHandler.java | 53 ++++++++++++++++
.../query/PubsubNodeArchiveQueryHandler.java} | 52 +++++++---------
.../modules/extension/xep0313_mam/query/Query.java | 17 +++---
.../xep0313_mam/query/UserArchiveQueryHandler.java | 71 ++++++++++++++++++++++
9 files changed, 191 insertions(+), 87 deletions(-)
copy server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/{MessageUnpagedRequest.java => MAMQueryHandler.java} (67%)
create mode 100644 server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MUCArchiveQueryHandler.java
copy server/{core/src/test/java/org/apache/vysper/xmpp/protocol/CallTestStanzaHandlerResponse.java => extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/PubsubNodeArchiveQueryHandler.java} (50%)
create mode 100644 server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/UserArchiveQueryHandler.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 315535d1a1e50ee403682bfab91767c2e8a0fd32
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 | 6 ++
.../core/base/handler/AcceptedMessageEvent.java | 75 +++++++++++++++
.../modules/core/base/handler/MessageHandler.java | 27 ++++--
.../xmpp/stanza/dataforms/DataFormParser.java | 4 +-
.../stanza/dataforms/DataFormParserTestCase.java | 4 +-
server/extensions/pom.xml | 4 +-
server/extensions/xep0313-mam/pom.xml | 64 +++++++++++++
.../xep0313_mam/MAMEventListenerDictionary.java | 38 ++++++++
.../modules/extension/xep0313_mam/MAMModule.java | 103 +++++++++++++++++++++
.../extension/xep0313_mam/MAMNamespaceURIs.java | 33 +++++++
.../xep0313_mam/archiving/SimpleMessage.java | 57 ++++++++++++
.../archiving/user/UserMessageListener.java | 81 ++++++++++++++++
.../extension/xep0313_mam/query/MAMIQHandler.java | 75 +++++++++++++++
.../xep0313_mam/query/MAMQueryHandler.java | 38 ++++++++
.../xep0313_mam/query/MUCArchiveQueryHandler.java | 53 +++++++++++
.../xep0313_mam/query/MessageUnpagedRequest.java | 46 +++++++++
.../query/PubsubNodeArchiveQueryHandler.java | 49 ++++++++++
.../modules/extension/xep0313_mam/query/Query.java | 71 ++++++++++++++
.../xep0313_mam/query/UserArchiveQueryHandler.java | 71 ++++++++++++++
.../modules/extension/xep0313_mam/query/X.java | 77 +++++++++++++++
.../extension/xep0313_mam/spi/ArchivedMessage.java | 34 +++++++
.../xep0313_mam/spi/ArchivedMessages.java | 47 ++++++++++
.../extension/xep0313_mam/spi/DateTimeFilter.java | 48 ++++++++++
.../modules/extension/xep0313_mam/spi/Message.java | 35 +++++++
.../xep0313_mam/spi/MessagePageRequest.java | 36 +++++++
.../xep0313_mam/spi/SimpleArchivedMessage.java | 60 ++++++++++++
.../xep0313_mam/spi/SimpleArchivedMessages.java | 67 ++++++++++++++
.../xep0313_mam/spi/user/UserMessageArchive.java | 46 +++++++++
.../xep0313_mam/spi/user/UserMessageArchives.java | 34 +++++++
.../extension/xep0313_mam/MAMModuleTest.java | 77 +++++++++++++++
30 files changed, 1445 insertions(+), 15 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..65ee034 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;
@@ -122,6 +123,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 f5ed4c8..1ae49c6 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;
@@ -40,6 +41,7 @@ import org.apache.vysper.xmpp.stanza.XMPPCoreStanza;
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class MessageHandler extends XMPPCoreStanzaHandler {
+
public String getName() {
return "message";
}
@@ -87,13 +89,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()) {
@@ -116,17 +113,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/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/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..be68839
--- /dev/null
+++ b/server/extensions/xep0313-mam/pom.xml
@@ -0,0 +1,64 @@
+<?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>
+ </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..59d9770
--- /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.archiving.user.UserMessageListener;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMIQHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchives;
+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((UserMessageArchives) serverRuntimeContext.getStorageProvider(UserMessageArchives.class),
+ "Could not find an instance of " + UserMessageArchives.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/archiving/SimpleMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/archiving/SimpleMessage.java
new file mode 100644
index 0000000..07f9e60
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/archiving/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.archiving;
+
+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/archiving/user/UserMessageListener.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/archiving/user/UserMessageListener.java
new file mode 100644
index 0000000..133d353
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/archiving/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.archiving.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.archiving.SimpleMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchive;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchives;
+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();
+ UserMessageArchives archives = requireNonNull(
+ (UserMessageArchives) serverRuntimeContext.getStorageProvider(UserMessageArchives.class),
+ "Could not find an instance of " + UserMessageArchives.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<UserMessageArchive> userArchive = archives.retrieve(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/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..4c4999c
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.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.extension.xep0313_mam.query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+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.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() {
+ List<MAMQueryHandler> queryHandlers = new ArrayList<>();
+ queryHandlers.add(new PubsubNodeArchiveQueryHandler());
+ queryHandlers.add(new MUCArchiveQueryHandler());
+ queryHandlers.add(new UserArchiveQueryHandler());
+ this.queryHandlers = Collections.unmodifiableList(queryHandlers);
+ }
+
+ @Override
+ protected boolean verifyInnerElement(Stanza stanza) {
+ return verifyInnerElementWorker(stanza, Query.ELEMENT_NAME)
+ && verifyInnerNamespace(stanza, MAMNamespaceURIs.CORE);
+ }
+
+ @Override
+ protected Stanza handleSet(IQStanza stanza, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ Query query;
+ try {
+ query = new Query(stanza);
+ } catch (XMLSemanticError xmlSemanticError) {
+ return ServerErrorResponses.getStanzaError(StanzaErrorCondition.NOT_ACCEPTABLE, stanza,
+ StanzaErrorType.CANCEL, null, null, null);
+ }
+
+ return queryHandlers.stream().map(handler -> handler.handle(query, serverRuntimeContext, sessionContext))
+ .filter(Optional::isPresent).map(Optional::get).findFirst()
+ .orElseGet(() -> 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..59524e3
--- /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.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 {
+
+ /**
+ * @return Empty if this handler can't handle the provided query.
+ */
+ Optional<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/MUCArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MUCArchiveQueryHandler.java
new file mode 100644
index 0000000..db5fcb9
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MUCArchiveQueryHandler.java
@@ -0,0 +1,53 @@
+/*
+ * 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.xmpp.addressing.Entity;
+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 Optional<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ Entity queriedEntity = ofNullable(query.iqStanza().getTo()).orElse(sessionContext.getInitiatingEntity());
+ if (serverRuntimeContext.getServerEnitity().getDomain().equals(queriedEntity.getDomain())) {
+ return Optional.empty();
+ }
+
+ // MUC archives is not yet implemented
+ Stanza stanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+ query.iqStanza(), StanzaErrorType.MODIFY,
+ "Multi-User Chat message archive feature is not yet implemented", null, null);
+ return Optional.of(stanza);
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MessageUnpagedRequest.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MessageUnpagedRequest.java
new file mode 100644
index 0000000..622357c
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MessageUnpagedRequest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.Optional;
+import java.util.OptionalLong;
+
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessagePageRequest;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MessageUnpagedRequest implements MessagePageRequest {
+
+ @Override
+ public OptionalLong pageSize() {
+ return OptionalLong.empty();
+ }
+
+ @Override
+ public Optional<String> firstMessageId() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<String> lastMessageId() {
+ return Optional.empty();
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/PubsubNodeArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/PubsubNodeArchiveQueryHandler.java
new file mode 100644
index 0000000..bf320b5
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/PubsubNodeArchiveQueryHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.Optional;
+
+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 Optional<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ if (!query.getNode().isPresent()) {
+ return Optional.empty();
+ }
+
+ // PubSub node archives is not yet implemented
+ Stanza stanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+ query.iqStanza(), StanzaErrorType.CANCEL, "Pubsub node message archive feature is not yet implemented",
+ null, null);
+ return Optional.of(stanza);
+ }
+}
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..9799a1e
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/Query.java
@@ -0,0 +1,71 @@
+/*
+ * 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.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 Optional<X> getX() throws XMLSemanticError {
+ return ofNullable(element.getSingleInnerElementsNamed(X.ELEMENT_NAME)).map(X::new);
+ }
+
+ public IQStanza iqStanza() {
+ return iqStanza;
+ }
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/UserArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/UserArchiveQueryHandler.java
new file mode 100644
index 0000000..b919b76
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/UserArchiveQueryHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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 java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchive;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchives;
+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 Optional<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext,
+ SessionContext sessionContext) {
+ Entity initiatingEntity = sessionContext.getInitiatingEntity();
+ Entity queriedEntity = ofNullable(query.iqStanza().getTo()).orElse(initiatingEntity);
+ if (!sessionContext.getInitiatingEntity().getBareJID().equals(queriedEntity.getBareJID())) {
+ // The initiating user is trying to read the archive of another user
+ Stanza forbidden = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FORBIDDEN, query.iqStanza(),
+ StanzaErrorType.CANCEL,
+ "Entity " + initiatingEntity + " is not allowed to query " + queriedEntity + " message archives.",
+ null, null);
+ return Optional.of(forbidden);
+ }
+
+ UserMessageArchives archives = requireNonNull(
+ (UserMessageArchives) serverRuntimeContext.getStorageProvider(UserMessageArchives.class),
+ "Could not find an instance of " + UserMessageArchives.class);
+
+ Entity queriedEntityBareJid = queriedEntity.getBareJID();
+ Optional<UserMessageArchive> archive = archives.retrieve(queriedEntityBareJid);
+ if (!archive.isPresent()) {
+ Stanza notFound = ServerErrorResponses.getStanzaError(StanzaErrorCondition.ITEM_NOT_FOUND, query.iqStanza(),
+ StanzaErrorType.CANCEL, "No user message archive found for entity " + queriedEntityBareJid, null,
+ null);
+ return Optional.of(notFound);
+ }
+
+ return null;
+ }
+}
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..f0174ca
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java
@@ -0,0 +1,77 @@
+/*
+ * 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.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.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.stanza.dataforms.DataFormParser;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class X {
+
+ 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();
+ }
+
+ 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> getStart() {
+ return getZonedDateTime("start");
+ }
+
+ public Optional<ZonedDateTime> getEnd() {
+ 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..4cb6c54
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java
@@ -0,0 +1,47 @@
+/*
+ * 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.OptionalLong;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface ArchivedMessages {
+
+ List<ArchivedMessage> list();
+
+ /**
+ * 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.
+ */
+ OptionalLong firstMessageIndex();
+
+ /**
+ * @return The total number of messages that could be retrieved by paging
+ * through the pages.
+ */
+ OptionalLong 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/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/MessagePageRequest.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageRequest.java
new file mode 100644
index 0000000..37a9b46
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageRequest.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 MessagePageRequest {
+
+ OptionalLong 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..2606e41
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java
@@ -0,0 +1,67 @@
+/*
+ * 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.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 OptionalLong firstMessageIndex() {
+ return ofNullable(firstMessageIndex).map(OptionalLong::of).orElse(OptionalLong.empty());
+ }
+
+ @Override
+ public OptionalLong totalNumberOfMessages() {
+ return ofNullable(totalNumberOfMessages).map(OptionalLong::of).orElse(OptionalLong.empty());
+ }
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchive.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchive.java
new file mode 100644
index 0000000..82e3a5d
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchive.java
@@ -0,0 +1,46 @@
+/*
+ * 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.user;
+
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.DateTimeFilter;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.Message;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessagePageRequest;
+
+/**
+ * <a href=
+ * "https://xmpp.org/extensions/xep-0313.html#business-storeret-user-archives">User
+ * Archives</a>
+ *
+ * @author Réda Housni Alaoui
+ */
+public interface UserMessageArchive {
+
+ /**
+ * 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 fetch(DateTimeFilter dateTimeRange, MessagePageRequest pageRequest);
+
+}
diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchives.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchives.java
new file mode 100644
index 0000000..8620c75
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/user/UserMessageArchives.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.user;
+
+import java.util.Optional;
+
+import org.apache.vysper.storage.StorageProvider;
+import org.apache.vysper.xmpp.addressing.Entity;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface UserMessageArchives extends StorageProvider {
+
+ Optional<UserMessageArchive> retrieve(Entity userBareJid);
+
+}
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..e5d493b
--- /dev/null
+++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.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(1, infoElements.size());
+ Assert.assertTrue(infoElements.get(0) instanceof Feature);
+ Assert.assertEquals(MAMNamespaceURIs.CORE, ((Feature) infoElements.get(0)).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