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 99aa9941cb39ca223fbeda390b53902ab740e07f
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         |  11 +-
 .../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              |  70 +++++++++
 .../xep0313_mam/MAMEventListenerDictionary.java    |  38 +++++
 .../modules/extension/xep0313_mam/MAMModule.java   | 103 +++++++++++++
 .../extension/xep0313_mam/MAMNamespaceURIs.java    |  33 ++++
 .../extension/xep0313_mam/SimpleMessage.java       |  57 +++++++
 .../xep0313_mam/muc/MUCArchiveQueryHandler.java    |  58 +++++++
 .../pubsub/PubsubNodeArchiveQueryHandler.java      |  54 +++++++
 .../xep0313_mam/query/ArchiveEntityFilter.java     |  77 ++++++++++
 .../extension/xep0313_mam/query/ArchiveQuery.java  |  59 ++++++++
 .../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   |  45 ++++++
 .../modules/extension/xep0313_mam/query/Query.java |  77 ++++++++++
 .../extension/xep0313_mam/query/QuerySet.java      |  58 +++++++
 .../modules/extension/xep0313_mam/query/X.java     |  84 +++++++++++
 .../extension/xep0313_mam/spi/ArchivedMessage.java |  34 +++++
 .../xep0313_mam/spi/ArchivedMessages.java          |  51 +++++++
 .../extension/xep0313_mam/spi/DateTimeFilter.java  |  48 ++++++
 .../extension/xep0313_mam/spi/EntityFilter.java    |  39 +++++
 .../modules/extension/xep0313_mam/spi/Message.java |  35 +++++
 .../extension/xep0313_mam/spi/MessageArchive.java  |  43 ++++++
 .../xep0313_mam/spi/MessagePageFilter.java         |  36 +++++
 .../xep0313_mam/spi/SimpleArchivedMessage.java     |  60 ++++++++
 .../xep0313_mam/spi/SimpleArchivedMessages.java    |  74 +++++++++
 .../xep0313_mam/user/UserArchiveQueryHandler.java  |  93 ++++++++++++
 .../xep0313_mam/user/UserMessageListener.java      |  81 ++++++++++
 .../xep0313_mam/user/spi/UserMessageArchives.java  |  35 +++++
 .../extension/xep0313_mam/MAMModuleTest.java       |  82 ++++++++++
 .../xep0313_mam/ServerRuntimeContextMock.java      | 166 +++++++++++++++++++++
 .../extension/xep0313_mam/SessionContextMock.java  | 145 ++++++++++++++++++
 .../extension/xep0313_mam/StanzaAssert.java        |  37 +++++
 .../query/MatchingArchivedMessageResultsTest.java  | 148 ++++++++++++++++++
 .../xep0313_mam/spi/MessageArchiveMock.java        |  61 ++++++++
 .../xep0313_mam/user/UserMessageListenerTest.java  | 127 ++++++++++++++++
 .../user/spi/UserMessageArchivesMock.java          |  47 ++++++
 47 files changed, 2698 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..fc08f5b 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
@@ -44,7 +44,7 @@ import org.apache.vysper.xmpp.protocol.NamespaceURIs;
  */
 public class Set {
 
-    private static final String ELEMENT_NAME = "set";
+    public static final String ELEMENT_NAME = "set";
 
     private static final String FIRST = "first";
 
@@ -64,6 +64,15 @@ public class Set {
         this.element = element;
     }
 
+    public static Set empty() {
+        return new Set(new 
XMLElement(NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT, ELEMENT_NAME, null,
+                Collections.emptyList(), Collections.emptyList()));
+    }
+
+    public XMLElement element() {
+        return element;
+    }
+
     public static Builder builder() {
         return new Builder();
     }
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..5046b8b
--- /dev/null
+++ b/server/extensions/xep0313-mam/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+  <!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements. See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to you under the Apache License, Version
+    2.0 (the "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0 
+    
+    Unless required by applicable law or agreed to in writing, software 
+    distributed under the License is distributed on an "AS IS" BASIS, 
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+    See the License for the specific language governing permissions and 
+    limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <parent>
+    <artifactId>vysper-extensions</artifactId>
+    <groupId>org.apache.vysper</groupId>
+    <version>0.8-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.vysper.extensions</groupId>
+  <artifactId>xep0313-mam</artifactId>
+  <name>Apache Vysper XEP-0313 Message Archive Management</name>
+  <version>0.8-SNAPSHOT</version>
+  
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.vysper</groupId>
+      <artifactId>spec-compliance</artifactId>
+      <optional>true</optional>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.vysper</groupId>
+      <artifactId>vysper-core</artifactId>
+    </dependency>
+    
+    <!-- Runtime dependencies -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+      
+    <!-- Test dependencies -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java
new file mode 100644
index 0000000..de4126e
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMEventListenerDictionary.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import org.apache.vysper.event.EventListener;
+import org.apache.vysper.event.EventListenerDictionary;
+
+import java.util.Set;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMEventListenerDictionary implements EventListenerDictionary {
+       
+       
+       
+       @Override
+       public Set<EventListener<?>> get(Class<?> eventType) {
+               return null;
+       }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java
new file mode 100644
index 0000000..b782b20
--- /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.user.UserMessageListener;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMIQHandler;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.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/SimpleMessage.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java
new file mode 100644
index 0000000..9e3a42b
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.Message;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleMessage implements Message {
+
+    private final MessageStanza stanza;
+
+    private final ZonedDateTime dateTime;
+
+    public SimpleMessage(MessageStanza stanza) {
+        this.stanza = requireNonNull(stanza);
+        this.dateTime = ZonedDateTime.now();
+    }
+
+    @Override
+    public MessageStanza stanza() {
+        return stanza;
+    }
+
+    @Override
+    public ZonedDateTime dateTime() {
+        return dateTime;
+    }
+
+    @Override
+    public String toString() {
+        return "SimpleMessage{" + "stanza=" + stanza + ", dateTime=" + 
dateTime + '}';
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java
new file mode 100644
index 0000000..a50c29f
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.muc;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MUCArchiveQueryHandler implements MAMQueryHandler {
+
+    @Override
+    public boolean supports(Query query, ServerRuntimeContext 
serverRuntimeContext, SessionContext sessionContext) {
+        Entity queriedEntity = 
ofNullable(query.iqStanza().getTo()).orElse(sessionContext.getInitiatingEntity());
+        return 
!serverRuntimeContext.getServerEnitity().getDomain().equals(queriedEntity.getDomain());
+    }
+
+    @Override
+    public List<Stanza> handle(Query query, ServerRuntimeContext 
serverRuntimeContext,
+                               SessionContext sessionContext) {
+        // MUC archives is not yet implemented
+        Stanza notImplemented = 
ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+                query.iqStanza(), StanzaErrorType.MODIFY,
+                "Multi-User Chat message archive feature is not yet 
implemented", null, null);
+        return Collections.singletonList(notImplemented);
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java
new file mode 100644
index 0000000..eba5826
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.pubsub;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class PubsubNodeArchiveQueryHandler implements MAMQueryHandler {
+
+    @Override
+    public boolean supports(Query query, ServerRuntimeContext 
serverRuntimeContext, SessionContext sessionContext) {
+        return query.getNode().isPresent();
+    }
+
+    @Override
+    public List<Stanza> handle(Query query, ServerRuntimeContext 
serverRuntimeContext,
+                               SessionContext sessionContext) {
+        // PubSub node archives is not yet implemented
+        Stanza notImplemented = 
ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED,
+                query.iqStanza(), StanzaErrorType.CANCEL, "Pubsub node message 
archive feature is not yet implemented",
+                null, null);
+        return Collections.singletonList(notImplemented);
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java
new file mode 100644
index 0000000..cf77509
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.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.Objects.requireNonNull;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.EntityFilter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveEntityFilter implements EntityFilter {
+
+    private final Entity with;
+
+    private final Type type;
+
+    private final boolean ignoreResource;
+
+    public ArchiveEntityFilter(Entity archiveId, Entity with) {
+        this.with = requireNonNull(with);
+        if (archiveId.equals(with)) {
+            // If the 'with' field's value is the bare JID of the archive, the 
server must
+            // only return results where both
+            // the 'to' and 'from' match the bare JID (either as bare or by 
ignoring the
+            // resource), as otherwise every
+            // message in the archive would match
+            this.type = Type.TO_AND_FROM;
+            this.ignoreResource = true;
+        } else if (!with.isResourceSet()) {
+            // If (and only if) the supplied JID is a bare JID (i.e. no 
resource is
+            // present),
+            // then the server SHOULD return messages if their bare to/from 
address for a
+            // user archive would match it.
+            this.type = Type.TO_OR_FROM;
+            this.ignoreResource = true;
+        } else {
+            // A message in a user's archive matches if the JID matches either 
the to or
+            // from of the message.
+            this.type = Type.TO_OR_FROM;
+            this.ignoreResource = false;
+        }
+    }
+
+    @Override
+    public Entity entity() {
+        return with;
+    }
+
+    @Override
+    public Type type() {
+        return type;
+    }
+
+    @Override
+    public boolean ignoreResource() {
+        return ignoreResource;
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.java
new file mode 100644
index 0000000..e0ba4c4
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveQuery.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.query;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.addressing.Entity;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ArchiveQuery {
+
+    private final MessageArchive archive;
+
+    private final Entity archiveId;
+
+    private final Query query;
+
+    public ArchiveQuery(MessageArchive archive, Entity archiveId, Query query) 
{
+        this.archive = requireNonNull(archive);
+        this.archiveId = requireNonNull(archiveId);
+        this.query = requireNonNull(query);
+    }
+
+    public ArchivedMessages execute() throws XMLSemanticError {
+        X x = query.getX();
+        QuerySet querySet = query.getSet();
+        Optional<Entity> with = query.getX().getWith();
+        if (!with.isPresent()) {
+            return archive.fetchAll(x, querySet);
+        } else {
+            return archive.fetch(new ArchiveEntityFilter(archiveId, 
with.get()), x, querySet);
+        }
+    }
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MAMIQHandler.java
new file mode 100644
index 0000000..5a93088
--- /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.muc.MUCArchiveQueryHandler;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.pubsub.PubsubNodeArchiveQueryHandler;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserArchiveQueryHandler;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.response.ServerErrorResponses;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.stanza.StanzaErrorCondition;
+import org.apache.vysper.xmpp.stanza.StanzaErrorType;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MAMIQHandler extends DefaultIQHandler {
+
+    private final List<MAMQueryHandler> queryHandlers;
+
+    public MAMIQHandler() {
+        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..000bce3
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/MessageUnpagedRequest.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.query;
+
+import java.util.Optional;
+
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessagePageFilter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MessageUnpagedRequest implements MessagePageFilter {
+
+    @Override
+    public Optional<Long> pageSize() {
+        return Optional.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..9a20880
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/Query.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.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import 
org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.stanza.IQStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class Query {
+
+    public static final String ELEMENT_NAME = "query";
+
+    private final IQStanza iqStanza;
+
+    private final XMLElement element;
+
+    public Query(IQStanza iqStanza) throws XMLSemanticError {
+        this.iqStanza = iqStanza;
+        this.element = iqStanza.getSingleInnerElementsNamed(ELEMENT_NAME);
+        if (!ELEMENT_NAME.equals(element.getName())) {
+            throw new IllegalArgumentException(
+                    "Query element must be named '" + ELEMENT_NAME + "' 
instead of '" + element.getName() + "'");
+        }
+        if (!MAMNamespaceURIs.CORE.equals(element.getNamespaceURI())) {
+            throw new IllegalArgumentException("Query element must be bound to 
namespace uri '" + MAMNamespaceURIs.CORE
+                    + "' instead of '" + element.getNamespaceURI() + "'");
+        }
+    }
+
+    public Optional<String> getQueryId() {
+        return ofNullable(element.getAttributeValue("queryid"));
+    }
+
+    public Optional<String> getNode() {
+        return ofNullable(element.getAttributeValue("node"));
+    }
+
+    public X getX() throws XMLSemanticError {
+        return 
ofNullable(element.getSingleInnerElementsNamed(X.ELEMENT_NAME)).map(X::new).orElseGet(X::empty);
+    }
+
+    public QuerySet getSet() throws XMLSemanticError {
+        return 
ofNullable(element.getSingleInnerElementsNamed(Set.ELEMENT_NAME)).map(Set::new).map(QuerySet::new)
+                .orElseGet(QuerySet::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/QuerySet.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySet.java
new file mode 100644
index 0000000..e71988d
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/QuerySet.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;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import 
org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.Set;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessagePageFilter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class QuerySet implements MessagePageFilter {
+
+    private final Set set;
+
+    public QuerySet() {
+        this(Set.empty());
+    }
+
+    public QuerySet(Set set) {
+        this.set = requireNonNull(set);
+    }
+
+    @Override
+    public Optional<Long> pageSize() {
+        return Optional.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/X.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java
new file mode 100644
index 0000000..ca833c6
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/X.java
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.query;
+
+import static java.util.Optional.ofNullable;
+
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.datetime.DateTimeProfile;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.MAMNamespaceURIs;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.DateTimeFilter;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.apache.vysper.xmpp.stanza.dataforms.DataFormParser;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class X implements DateTimeFilter {
+
+    public static final String ELEMENT_NAME = "x";
+
+    private final Map<String, Object> fields;
+
+    public X(XMLElement element) {
+        if (!ELEMENT_NAME.equals(element.getName())) {
+            throw new IllegalArgumentException(
+                    "Query element must be named '" + ELEMENT_NAME + "' 
instead of '" + element.getName() + "'");
+        }
+        if (!NamespaceURIs.JABBER_X_DATA.equals(element.getNamespaceURI())) {
+            throw new IllegalArgumentException("Query element must be bound to 
namespace uri '" + MAMNamespaceURIs.CORE
+                    + "' instead of '" + element.getNamespaceURI() + "'");
+        }
+        fields = new DataFormParser(element).extractFieldValues();
+    }
+
+    public static X empty() {
+        return new X(new XMLElement(NamespaceURIs.JABBER_X_DATA, ELEMENT_NAME, 
null, Collections.emptyList(),
+                Collections.emptyList()));
+    }
+
+    private Optional<String> getStringValue(String fieldName) {
+        return ofNullable(fields.get(fieldName)).map(String.class::cast);
+    }
+
+    private Optional<ZonedDateTime> getZonedDateTime(String fieldName) {
+        return getStringValue(fieldName).map(stringDate -> 
DateTimeProfile.getInstance().fromZonedDateTime(stringDate));
+    }
+
+    public Optional<Entity> getWith() {
+        return getStringValue("with").map(EntityImpl::parseUnchecked);
+    }
+
+    public Optional<ZonedDateTime> start() {
+        return getZonedDateTime("start");
+    }
+
+    public Optional<ZonedDateTime> end() {
+        return getZonedDateTime("end");
+    }
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java
new file mode 100644
index 0000000..3a20f30
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessage.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface ArchivedMessage extends Message {
+
+    /**
+     * @return The id of the message in its archive as defined in <a
+     *         
href="https://xmpp.org/extensions/xep-0313.html#archives_id";>Communicating
+     *         the archive ID</a>
+     */
+    String id();
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/ArchivedMessages.java
new file mode 100644
index 0000000..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/EntityFilter.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java
new file mode 100644
index 0000000..dc232a7
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface EntityFilter {
+
+    enum Type {
+        TO_OR_FROM, TO_AND_FROM;
+    }
+
+    Entity entity();
+
+    Type type();
+
+    boolean ignoreResource();
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java
new file mode 100644
index 0000000..0ef98e5
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface Message {
+
+    MessageStanza stanza();
+
+    ZonedDateTime dateTime();
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java
new file mode 100644
index 0000000..f8c56ce
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+/**
+ * <a href=
+ * "https://xmpp.org/extensions/xep-0313.html#business-storeret";>Storage and
+ * Retrieval Rules</a>
+ * 
+ * @author Réda Housni Alaoui
+ */
+public interface MessageArchive {
+
+    /**
+     * At a minimum, the server MUST store the <body> elements of a stanza. It 
is
+     * suggested that other elements that are used in a given deployment to
+     * supplement conversations (e.g. XHTML-IM payloads) are also stored. Other
+     * elements MAY be stored.
+     */
+    void archive(Message message);
+
+    ArchivedMessages fetchAll(DateTimeFilter dateTimeFilter, MessagePageFilter 
pageFilter);
+
+    ArchivedMessages fetch(EntityFilter entityFilter, DateTimeFilter 
dateTimeFilter, MessagePageFilter pageFilter);
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java
new file mode 100644
index 0000000..76cc182
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+import java.util.Optional;
+import java.util.OptionalLong;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface MessagePageFilter {
+       
+       Optional<Long> pageSize();
+       
+       Optional<String> firstMessageId();
+       
+       Optional<String> lastMessageId();
+       
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java
new file mode 100644
index 0000000..c884b98
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.ZonedDateTime;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SimpleArchivedMessage implements ArchivedMessage {
+
+    private final String id;
+
+    private final ZonedDateTime dateTime;
+
+    private final MessageStanza stanza;
+
+    public SimpleArchivedMessage(String id, ZonedDateTime dateTime, 
MessageStanza stanza) {
+        this.id = requireNonNull(id);
+        this.dateTime = requireNonNull(dateTime);
+        this.stanza = requireNonNull(stanza);
+    }
+
+    @Override
+    public MessageStanza stanza() {
+        return stanza;
+    }
+
+    @Override
+    public ZonedDateTime dateTime() {
+        return dateTime;
+    }
+
+    @Override
+    public String id() {
+        return id;
+    }
+
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java
new file mode 100644
index 0000000..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/user/UserArchiveQueryHandler.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java
new file mode 100644
index 0000000..24374e9
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java
@@ -0,0 +1,93 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.ArchiveQuery;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.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.MessageArchive;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.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<MessageArchive> 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 ArchiveQuery(archive.get(), 
archiveId, query).execute();
+        return new MatchingArchivedMessageResults(initiatingEntity, archiveId, 
query, archivedMessages).toStanzas();
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java
new file mode 100644
index 0000000..02ac691
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import org.apache.vysper.event.EventListener;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent;
+import org.apache.vysper.xmpp.modules.core.base.handler.XMPPCoreStanzaHandler;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SimpleMessage;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.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<MessageArchive> 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/user/spi/UserMessageArchives.java
 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/spi/UserMessageArchives.java
new file mode 100644
index 0000000..79c3f6a
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/spi/UserMessageArchives.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.user.spi;
+
+import java.util.Optional;
+
+import org.apache.vysper.storage.StorageProvider;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public interface UserMessageArchives extends StorageProvider {
+
+    Optional<MessageArchive> 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..4de303a
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java
@@ -0,0 +1,82 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.vysper.xmpp.modules.servicediscovery.management.Feature;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoElement;
+import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoRequest;
+import 
org.apache.vysper.xmpp.modules.servicediscovery.management.ServerInfoRequestListener;
+import 
org.apache.vysper.xmpp.modules.servicediscovery.management.ServiceDiscoveryRequestException;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+/**
+ * @author The Apache MINA Project (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(3, infoElements.size());
+        Assert.assertTrue(infoElements.get(0) instanceof Feature);
+        Assert.assertTrue(infoElements.get(1) instanceof Feature);
+        Assert.assertTrue(infoElements.get(2) instanceof Feature);
+        Assert.assertEquals(MAMNamespaceURIs.CORE, ((Feature) 
infoElements.get(0)).getVar());
+        Assert.assertEquals(MAMNamespaceURIs.XEP0359_STANZA_IDS, ((Feature) 
infoElements.get(1)).getVar());
+        Assert.assertEquals(NamespaceURIs.JABBER_X_DATA, ((Feature) 
infoElements.get(2)).getVar());
+    }
+
+    @Test
+    public void getServerInfosForWithNode() throws 
ServiceDiscoveryRequestException {
+        List<ServerInfoRequestListener> serverInfoRequestListeners = new 
ArrayList<>();
+        tested.addServerInfoRequestListeners(serverInfoRequestListeners);
+        Assert.assertEquals(1, serverInfoRequestListeners.size());
+
+        Assert.assertNull(
+                serverInfoRequestListeners.get(0).getServerInfosFor(new 
InfoRequest(null, null, "node", null)));
+    }
+
+}
\ No newline at end of file
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
new file mode 100644
index 0000000..badf076
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java
@@ -0,0 +1,166 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.vysper.event.EventBus;
+import org.apache.vysper.storage.StorageProvider;
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.authentication.UserAuthentication;
+import org.apache.vysper.xmpp.delivery.StanzaRelay;
+import org.apache.vysper.xmpp.modules.Module;
+import org.apache.vysper.xmpp.modules.ServerRuntimeContextService;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.UserMessageArchives;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.UserMessageArchivesMock;
+import org.apache.vysper.xmpp.protocol.StanzaHandler;
+import org.apache.vysper.xmpp.protocol.StanzaProcessor;
+import org.apache.vysper.xmpp.server.ServerFeatures;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.components.Component;
+import org.apache.vysper.xmpp.server.s2s.XMPPServerConnectorRegistry;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.apache.vysper.xmpp.state.presence.LatestPresenceCache;
+import org.apache.vysper.xmpp.state.resourcebinding.ResourceRegistry;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ServerRuntimeContextMock implements ServerRuntimeContext {
+
+    private UserMessageArchivesMock userMessageArchives;
+
+    public UserMessageArchivesMock givenUserMessageArchives() {
+        userMessageArchives = new UserMessageArchivesMock();
+        return userMessageArchives;
+    }
+
+    @Override
+    public StanzaHandler getHandler(Stanza stanza) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getNextSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Entity getServerEnitity() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDefaultXMLLang() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StanzaProcessor getStanzaProcessor() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StanzaRelay getStanzaRelay() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServerFeatures getServerFeatures() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SSLContext getSslContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public UserAuthentication getUserAuthentication() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ResourceRegistry getResourceRegistry() {
+        return mock(ResourceRegistry.class);
+    }
+
+    @Override
+    public LatestPresenceCache getPresenceCache() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void 
registerServerRuntimeContextService(ServerRuntimeContextService service) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServerRuntimeContextService getServerRuntimeContextService(String 
name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StorageProvider getStorageProvider(Class<? extends StorageProvider> 
clazz) {
+        if (UserMessageArchives.class.equals(clazz)) {
+            return userMessageArchives;
+        }
+        return null;
+    }
+
+    @Override
+    public void registerComponent(Component component) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StanzaProcessor getComponentStanzaProcessor(Entity entity) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public XMPPServerConnectorRegistry getServerConnectorRegistry() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Module> getModules() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T getModule(Class<T> clazz) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addModule(Module module) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EventBus getEventBus() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java
new file mode 100644
index 0000000..5b69042
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java
@@ -0,0 +1,145 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.server.ServerRuntimeContext;
+import org.apache.vysper.xmpp.server.SessionContext;
+import org.apache.vysper.xmpp.server.SessionState;
+import org.apache.vysper.xmpp.state.resourcebinding.BindException;
+import org.apache.vysper.xmpp.writer.StanzaWriter;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class SessionContextMock implements SessionContext {
+
+    private ServerRuntimeContext serverRuntimeContext;
+
+    private Entity initiatingEntity;
+
+    public void givenServerRuntimeContext(ServerRuntimeContext 
serverRuntimeContext) {
+        this.serverRuntimeContext = serverRuntimeContext;
+    }
+
+    public void givenInitiatingEntity(Entity initiatingEntity) {
+        this.initiatingEntity = initiatingEntity;
+    }
+
+    @Override
+    public ServerRuntimeContext getServerRuntimeContext() {
+        return serverRuntimeContext;
+    }
+
+    @Override
+    public boolean isRemotelyInitiatedSession() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Entity getInitiatingEntity() {
+        return initiatingEntity;
+    }
+
+    @Override
+    public void setInitiatingEntity(Entity entity) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isServerToServer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setServerToServer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setClientToServer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SessionState getState() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getXMLLang() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setXMLLang(String languageCode) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StanzaWriter getResponseWriter() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void endSession(SessionTerminationCause terminationCause) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Entity getServerJID() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void switchToTLS(boolean delayed, boolean clientTls) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setIsReopeningXMLStream() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String bindResource() throws BindException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String nextSequenceValue() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object putAttribute(String key, Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object getAttribute(String key) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java
new file mode 100644
index 0000000..bbf9369
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam;
+
+import org.apache.vysper.xml.fragment.Renderer;
+import org.apache.vysper.xmpp.stanza.Stanza;
+import org.junit.Assert;
+
+public class StanzaAssert {
+
+    public static void assertEquals(Stanza expected, Stanza actual) {
+        try {
+            Assert.assertEquals(expected, actual);
+        } catch(Throwable e) {
+            // print something useful
+            Assert.assertEquals(new Renderer(expected).getComplete(), new 
Renderer(actual).getComplete());
+        }
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/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
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java
new file mode 100644
index 0000000..a9387b8
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchiveMock.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class MessageArchiveMock implements MessageArchive {
+
+    private final Queue<Message> messages = new LinkedList<>();
+
+    @Override
+    public void archive(Message message) {
+        messages.add(message);
+    }
+
+    @Override
+    public ArchivedMessages fetchAll(DateTimeFilter dateTimeRange, 
MessagePageFilter pageRequest) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ArchivedMessages fetch(EntityFilter entityFilter, DateTimeFilter 
dateTimeFilter,
+            MessagePageFilter pageFilter) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void assertUniqueArchivedMessageStanza(MessageStanza messageStanza) 
{
+        assertEquals(1, messages.size());
+        assertEquals(messageStanza, messages.peek().stanza());
+    }
+
+    public void assertEmpty() {
+        assertTrue(messages.isEmpty());
+    }
+}
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java
new file mode 100644
index 0000000..6f9c034
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.addressing.EntityImpl;
+import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.ServerRuntimeContextMock;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SessionContextMock;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchiveMock;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi.UserMessageArchivesMock;
+import org.apache.vysper.xmpp.stanza.MessageStanza;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class UserMessageListenerTest {
+
+    private static final Entity JULIET_IN_CHAMBER = 
EntityImpl.parseUnchecked("jul...@capulet.lit/chamber");
+
+    private static final Entity ROMEO_IN_ORCHARD = 
EntityImpl.parseUnchecked("ro...@montague.lit/orchard");
+
+    private static final Entity MACBETH_IN_KITCHEN = 
EntityImpl.parseUnchecked("macb...@shakespeare.lit/kitchen");
+
+    private static final Entity ALICE_IN_RABBIT_HOLE = 
EntityImpl.parseUnchecked("al...@carol.lit/rabbit-hole");
+
+    private static final Entity INITIATING_ENTITY = JULIET_IN_CHAMBER;
+
+    private MessageArchiveMock julietArchive;
+
+    private MessageArchiveMock romeoArchive;
+
+    private MessageArchiveMock macbethArchive;
+
+    private AcceptedMessageEvent acceptedMessage;
+
+    private MessageStanza messageStanza;
+
+    private UserMessageListener tested;
+
+    @Before
+    public void before() {
+        ServerRuntimeContextMock serverRuntimeContext = new 
ServerRuntimeContextMock();
+
+        UserMessageArchivesMock archives = 
serverRuntimeContext.givenUserMessageArchives();
+
+        julietArchive = archives.givenArchive(JULIET_IN_CHAMBER.getBareJID());
+        romeoArchive = archives.givenArchive(ROMEO_IN_ORCHARD.getBareJID());
+        macbethArchive = 
archives.givenArchive(MACBETH_IN_KITCHEN.getBareJID());
+
+        SessionContextMock sessionContext = new SessionContextMock();
+        sessionContext.givenInitiatingEntity(INITIATING_ENTITY);
+        sessionContext.givenServerRuntimeContext(serverRuntimeContext);
+
+        messageStanza = mock(MessageStanza.class);
+        acceptedMessage = mock(AcceptedMessageEvent.class);
+        
when(acceptedMessage.serverRuntimeContext()).thenReturn(serverRuntimeContext);
+        when(acceptedMessage.sessionContext()).thenReturn(sessionContext);
+        when(acceptedMessage.messageStanza()).thenReturn(messageStanza);
+
+        tested = new UserMessageListener();
+    }
+
+    @Test
+    public void outboundMessageHavingFrom() {
+        when(acceptedMessage.isOutbound()).thenReturn(true);
+        when(messageStanza.getFrom()).thenReturn(ROMEO_IN_ORCHARD);
+
+        tested.onEvent(acceptedMessage);
+
+        romeoArchive.assertUniqueArchivedMessageStanza(messageStanza);
+    }
+
+    @Test
+    public void outboundMessageWithoutFrom() {
+        when(acceptedMessage.isOutbound()).thenReturn(true);
+
+        tested.onEvent(acceptedMessage);
+
+        julietArchive.assertUniqueArchivedMessageStanza(messageStanza);
+    }
+
+    @Test
+    public void inboundMessage() {
+        when(acceptedMessage.isOutbound()).thenReturn(false);
+        when(messageStanza.getTo()).thenReturn(MACBETH_IN_KITCHEN);
+
+        tested.onEvent(acceptedMessage);
+
+        macbethArchive.assertUniqueArchivedMessageStanza(messageStanza);
+    }
+
+    @Test
+    public void unexistingArchive() {
+        when(acceptedMessage.isOutbound()).thenReturn(true);
+        when(messageStanza.getFrom()).thenReturn(ALICE_IN_RABBIT_HOLE);
+
+        tested.onEvent(acceptedMessage);
+
+        julietArchive.assertEmpty();
+        romeoArchive.assertEmpty();
+        macbethArchive.assertEmpty();
+    }
+
+}
\ No newline at end of file
diff --git 
a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/spi/UserMessageArchivesMock.java
 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/spi/UserMessageArchivesMock.java
new file mode 100644
index 0000000..3610792
--- /dev/null
+++ 
b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/spi/UserMessageArchivesMock.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *  
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.spi;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.vysper.xmpp.addressing.Entity;
+import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive;
+import 
org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchiveMock;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class UserMessageArchivesMock implements UserMessageArchives {
+
+    private final Map<Entity, MessageArchive> archiveById = new HashMap<>();
+
+    public MessageArchiveMock givenArchive(Entity archiveId) {
+        MessageArchiveMock userMessageArchiveMock = new MessageArchiveMock();
+        archiveById.put(archiveId, userMessageArchiveMock);
+        return userMessageArchiveMock;
+    }
+
+    @Override
+    public Optional<MessageArchive> retrieve(Entity userBareJid) {
+        return Optional.ofNullable(archiveById.get(userBareJid));
+    }
+}

Reply via email to