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 c3270ed66f889e06da6daccd7deeb9b7925b38ef
Author: Réda Housni Alaoui <reda.housniala...@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         |   6 +-
 .../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 +-
 .../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  |  78 +++++++++++
 .../xep0313_mam/query/MAMQueryHandler.java         |  38 ++++++
 .../query/MatchingArchivedMessageResult.java       | 109 +++++++++++++++
 .../query/MatchingArchivedMessageResults.java      |  89 +++++++++++++
 .../xep0313_mam/query/MessageUnpagedRequest.java   |  46 +++++++
 .../modules/extension/xep0313_mam/query/Query.java |  71 ++++++++++
 .../modules/extension/xep0313_mam/query/X.java     |  77 +++++++++++
 .../query/muc/MUCArchiveQueryHandler.java          |  58 ++++++++
 .../pubsub/PubsubNodeArchiveQueryHandler.java      |  54 ++++++++
 .../xep0313_mam/query/user/UserArchiveQuery.java   |  52 ++++++++
 .../query/user/UserArchiveQueryHandler.java        |  92 +++++++++++++
 .../extension/xep0313_mam/spi/ArchivedMessage.java |  34 +++++
 .../xep0313_mam/spi/ArchivedMessages.java          |  51 +++++++
 .../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    |  74 +++++++++++
 .../xep0313_mam/spi/user/UserMessageArchive.java   |  46 +++++++
 .../xep0313_mam/spi/user/UserMessageArchives.java  |  34 +++++
 .../extension/xep0313_mam/MAMModuleTest.java       |  77 +++++++++++
 .../extension/xep0313_mam/StanzaAssert.java        |  37 ++++++
 .../query/MatchingArchivedMessageResultsTest.java  | 148 +++++++++++++++++++++
 39 files changed, 1944 insertions(+), 17 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 (d...@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..8b50407 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
@@ -63,11 +63,15 @@ public class Set {
         }
         this.element = element;
     }
+    
+    public XMLElement element(){
+        return element;
+    }
 
     public static Builder builder() {
         return new Builder();
     }
-
+    
     public Optional<String> getAfter() throws XMLSemanticError {
         return getElementText("after");
     }
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 (d...@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/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..198b1db
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java
@@ -0,0 +1,78 @@
+/*
+ *  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.query.muc.MUCArchiveQueryHandler;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.pubsub.PubsubNodeArchiveQueryHandler;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.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() {
+        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 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/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/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/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/query/muc/MUCArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/muc/MUCArchiveQueryHandler.java
new file mode 100644
index 0000000..64ac71b
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/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.query.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/query/pubsub/PubsubNodeArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/pubsub/PubsubNodeArchiveQueryHandler.java
new file mode 100644
index 0000000..504efe3
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/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.query.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/user/UserArchiveQuery.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/user/UserArchiveQuery.java
new file mode 100644
index 0000000..a2115e8
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/user/UserArchiveQuery.java
@@ -0,0 +1,52 @@
+/*
+ *  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.user;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.X;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.user.UserMessageArchive;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+class UserArchiveQuery {
+
+    private final Query query;
+
+    private final UserMessageArchive userMessageArchive;
+
+    UserArchiveQuery(UserMessageArchive userMessageArchive, Query query) {
+        this.userMessageArchive = requireNonNull(userMessageArchive);
+        this.query = requireNonNull(query);
+    }
+
+    ArchivedMessages execute() throws XMLSemanticError {
+        Optional<X> x = query.getX();
+
+        return null;
+    }
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/user/UserArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/user/UserArchiveQueryHandler.java
new file mode 100644
index 0000000..53ada0e
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/user/UserArchiveQueryHandler.java
@@ -0,0 +1,92 @@
+/*
+ *  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.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.MatchingArchivedMessageResults;
+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.modules.extension.xep0313_mam.spi.ArchivedMessages;
+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 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));
+        }
+
+        UserMessageArchives archives = requireNonNull(
+                (UserMessageArchives) 
serverRuntimeContext.getStorageProvider(UserMessageArchives.class),
+                "Could not find an instance of " + UserMessageArchives.class);
+
+        Entity archiveId = archiveOwner.getBareJID();
+        Optional<UserMessageArchive> archive = archives.retrieve(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 
UserArchiveQuery(archive.get(), 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/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..073d373
--- /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<Integer> firstMessageIndex();
+
+    /**
+     * @return The total number of messages that could be retrieved by paging
+     *         through the pages.
+     */
+    Optional<Integer> 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..3a7ee76
--- /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 Integer firstMessageIndex;
+
+    private final Integer totalNumberOfMessages;
+
+    public SimpleArchivedMessages(List<ArchivedMessage> list) {
+        this(list, null, null);
+    }
+
+    public SimpleArchivedMessages(List<ArchivedMessage> list, Integer 
firstMessageIndex) {
+        this(list, firstMessageIndex, null);
+    }
+
+    public SimpleArchivedMessages(List<ArchivedMessage> list, Integer 
firstMessageIndex, Integer 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<Integer> firstMessageIndex() {
+        return ofNullable(firstMessageIndex);
+    }
+
+    @Override
+    public Optional<Integer> totalNumberOfMessages() {
+        return ofNullable(totalNumberOfMessages);
+    }
+}
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 (d...@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
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/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..17af2d8
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MatchingArchivedMessageResultsTest.java
@@ -0,0 +1,148 @@
+/*
+ *  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("jul...@capulet.lit/chamber");
+
+    private static final Entity ARCHIVE_ID = 
EntityImpl.parseUnchecked("jul...@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>");
+        requireNonNull(queryIqElement);
+        query = new Query(
+                new IQStanza(StanzaBuilder.createClone(queryIqElement, true, 
Collections.emptyList()).build()));
+
+        XMLElement messageElement = XMLParserUtil.parseRequiredDocument(
+                "<message xmlns='jabber:client' from=\"wi...@shakespeare.lit\" 
to=\"macb...@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, 0, 2);
+
+        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='jul...@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='wi...@shakespeare.lit' to='macb...@shakespeare.lit'>"
+                        + "        <body>Hail to thee</body>"
+                        + "        <stanza-id xmlns='urn:xmpp:sid:0' 
by='jul...@capulet.lit' id='28482-98726-73623'/>"
+                        + "</message></forwarded></result></message>"), true, 
null)
+                .build(), responseStanzas.get(0));
+
+        StanzaAssert.assertEquals(StanzaBuilder
+                .createClone(XMLParserUtil.parseRequiredDocument("<message 
to='jul...@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='wi...@shakespeare.lit' to='macb...@shakespeare.lit'>"
+                        + "        <body>Hail to thee</body>"
+                        + "        <stanza-id xmlns='urn:xmpp:sid:0' 
by='jul...@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(), 0, 50);
+
+        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

Reply via email to