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 b8efca1b58c08c1bbe481c876965420415dab2f7 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 | 56 +++++++ .../modules/extension/xep0313_mam/spi/Message.java | 35 +++++ .../extension/xep0313_mam/spi/MessageArchive.java | 43 ++++++ .../extension/xep0313_mam/spi/MessageArchives.java | 34 +++++ .../xep0313_mam/spi/MessagePageFilter.java | 36 +++++ .../xep0313_mam/spi/SimpleArchivedMessage.java | 60 ++++++++ .../xep0313_mam/spi/SimpleArchivedMessages.java | 74 +++++++++ .../xep0313_mam/user/UserArchiveQueryHandler.java | 93 ++++++++++++ .../xep0313_mam/user/UserMessageListener.java | 81 ++++++++++ .../extension/xep0313_mam/MAMModuleTest.java | 82 ++++++++++ .../xep0313_mam/ServerRuntimeContextMock.java | 166 +++++++++++++++++++++ .../extension/xep0313_mam/SessionContextMock.java | 145 ++++++++++++++++++ .../extension/xep0313_mam/StanzaAssert.java | 37 +++++ .../query/MatchingArchivedMessageResultsTest.java | 148 ++++++++++++++++++ .../xep0313_mam/spi/MessageArchiveMock.java | 61 ++++++++ .../xep0313_mam/spi/MessageArchivesMock.java | 45 ++++++ .../xep0313_mam/user/UserMessageListenerTest.java | 127 ++++++++++++++++ 47 files changed, 2712 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..987ac85 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModule.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang.StringUtils; +import org.apache.vysper.event.EventListenerDictionary; +import org.apache.vysper.event.SimpleEventListenerDictionary; +import org.apache.vysper.xmpp.modules.DefaultDiscoAwareModule; +import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMIQHandler; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.user.UserMessageListener; +import org.apache.vysper.xmpp.modules.servicediscovery.management.Feature; +import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoElement; +import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoRequest; +import org.apache.vysper.xmpp.modules.servicediscovery.management.ServerInfoRequestListener; +import org.apache.vysper.xmpp.protocol.HandlerDictionary; +import org.apache.vysper.xmpp.protocol.NamespaceHandlerDictionary; +import org.apache.vysper.xmpp.protocol.NamespaceURIs; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; + +/** + * A module for <a href="https://xmpp.org/extensions/xep-0313.html">XEP-0313 + * Message Archive Management</a> + * + * @author Réda Housni Alaoui + */ +public class MAMModule extends DefaultDiscoAwareModule implements ServerInfoRequestListener { + + private final UserMessageListener userMessageListener = new UserMessageListener(); + + @Override + public void initialize(ServerRuntimeContext serverRuntimeContext) { + super.initialize(serverRuntimeContext); + + requireNonNull((MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class), + "Could not find an instance of " + MessageArchives.class); + } + + @Override + public String getName() { + return "XEP-0313 Message Archive Management"; + } + + @Override + public String getVersion() { + return "0.6.3"; + } + + @Override + protected void addServerInfoRequestListeners(List<ServerInfoRequestListener> serverInfoRequestListeners) { + serverInfoRequestListeners.add(this); + } + + @Override + public List<InfoElement> getServerInfosFor(InfoRequest request) { + if (StringUtils.isNotEmpty(request.getNode())) { + return null; + } + + List<InfoElement> infoElements = new ArrayList<>(); + infoElements.add(new Feature(MAMNamespaceURIs.CORE)); + infoElements.add(new Feature(MAMNamespaceURIs.XEP0359_STANZA_IDS)); + infoElements.add(new Feature(NamespaceURIs.JABBER_X_DATA)); + return infoElements; + } + + @Override + protected void addHandlerDictionaries(List<HandlerDictionary> dictionary) { + MAMIQHandler iqHandler = new MAMIQHandler(); + dictionary.add(new NamespaceHandlerDictionary(MAMNamespaceURIs.CORE, iqHandler)); + } + + @Override + public Optional<EventListenerDictionary> getEventListenerDictionary() { + EventListenerDictionary dictionary = SimpleEventListenerDictionary.builder() + .register(AcceptedMessageEvent.class, userMessageListener).build(); + return Optional.of(dictionary); + } +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java new file mode 100644 index 0000000..13a17a7 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMNamespaceURIs.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +/** + * @author Réda Housni Alaoui + */ +public class MAMNamespaceURIs { + + public static final String CORE = "urn:xmpp:mam:2"; + public static final String XEP0359_STANZA_IDS = "urn:xmpp:sid:0"; + + private MAMNamespaceURIs() { + } + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java new file mode 100644 index 0000000..9e3a42b --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SimpleMessage.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import static java.util.Objects.requireNonNull; + +import java.time.ZonedDateTime; + +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.Message; +import org.apache.vysper.xmpp.stanza.MessageStanza; + +/** + * @author Réda Housni Alaoui + */ +public class SimpleMessage implements Message { + + private final MessageStanza stanza; + + private final ZonedDateTime dateTime; + + public SimpleMessage(MessageStanza stanza) { + this.stanza = requireNonNull(stanza); + this.dateTime = ZonedDateTime.now(); + } + + @Override + public MessageStanza stanza() { + return stanza; + } + + @Override + public ZonedDateTime dateTime() { + return dateTime; + } + + @Override + public String toString() { + return "SimpleMessage{" + "stanza=" + stanza + ", dateTime=" + dateTime + '}'; + } +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java new file mode 100644 index 0000000..a50c29f --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/muc/MUCArchiveQueryHandler.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.muc; + +import static java.util.Optional.ofNullable; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.SessionContext; +import org.apache.vysper.xmpp.server.response.ServerErrorResponses; +import org.apache.vysper.xmpp.stanza.Stanza; +import org.apache.vysper.xmpp.stanza.StanzaErrorCondition; +import org.apache.vysper.xmpp.stanza.StanzaErrorType; + +/** + * @author Réda Housni Alaoui + */ +public class MUCArchiveQueryHandler implements MAMQueryHandler { + + @Override + public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) { + Entity queriedEntity = ofNullable(query.iqStanza().getTo()).orElse(sessionContext.getInitiatingEntity()); + return !serverRuntimeContext.getServerEnitity().getDomain().equals(queriedEntity.getDomain()); + } + + @Override + public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext, + SessionContext sessionContext) { + // MUC archives is not yet implemented + Stanza notImplemented = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED, + query.iqStanza(), StanzaErrorType.MODIFY, + "Multi-User Chat message archive feature is not yet implemented", null, null); + return Collections.singletonList(notImplemented); + } +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java new file mode 100644 index 0000000..eba5826 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/pubsub/PubsubNodeArchiveQueryHandler.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.pubsub; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.SessionContext; +import org.apache.vysper.xmpp.server.response.ServerErrorResponses; +import org.apache.vysper.xmpp.stanza.Stanza; +import org.apache.vysper.xmpp.stanza.StanzaErrorCondition; +import org.apache.vysper.xmpp.stanza.StanzaErrorType; + +/** + * @author Réda Housni Alaoui + */ +public class PubsubNodeArchiveQueryHandler implements MAMQueryHandler { + + @Override + public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) { + return query.getNode().isPresent(); + } + + @Override + public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext, + SessionContext sessionContext) { + // PubSub node archives is not yet implemented + Stanza notImplemented = ServerErrorResponses.getStanzaError(StanzaErrorCondition.FEATURE_NOT_IMPLEMENTED, + query.iqStanza(), StanzaErrorType.CANCEL, "Pubsub node message archive feature is not yet implemented", + null, null); + return Collections.singletonList(notImplemented); + } +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/ArchiveEntityFilter.java new file mode 100644 index 0000000..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..f274c11 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/EntityFilter.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import org.apache.vysper.xmpp.addressing.Entity; + +/** + * @author Réda Housni Alaoui + */ +public interface EntityFilter { + + enum Type { + /** + * The message 'to' or 'from' must match the provided entity + */ + TO_OR_FROM, + /** + * The message 'to' and 'from' must match the provided entity + */ + TO_AND_FROM; + } + + /** + * @return The entity to filter by + */ + Entity entity(); + + /** + * @return The type of filtering + */ + Type type(); + + /** + * @return True to ignore the filtering entity, the message 'to' and the message + * 'from' <b>resource part</b>. + */ + boolean ignoreResource(); + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java new file mode 100644 index 0000000..0ef98e5 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/Message.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import java.time.ZonedDateTime; + +import org.apache.vysper.xmpp.stanza.MessageStanza; + +/** + * @author Réda Housni Alaoui + */ +public interface Message { + + MessageStanza stanza(); + + ZonedDateTime dateTime(); + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java new file mode 100644 index 0000000..f8c56ce --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchive.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +/** + * <a href= + * "https://xmpp.org/extensions/xep-0313.html#business-storeret">Storage and + * Retrieval Rules</a> + * + * @author Réda Housni Alaoui + */ +public interface MessageArchive { + + /** + * At a minimum, the server MUST store the <body> elements of a stanza. It is + * suggested that other elements that are used in a given deployment to + * supplement conversations (e.g. XHTML-IM payloads) are also stored. Other + * elements MAY be stored. + */ + void archive(Message message); + + ArchivedMessages fetchAll(DateTimeFilter dateTimeFilter, MessagePageFilter pageFilter); + + ArchivedMessages fetch(EntityFilter entityFilter, DateTimeFilter dateTimeFilter, MessagePageFilter pageFilter); + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java new file mode 100644 index 0000000..e4df43b --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchives.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import java.util.Optional; + +import org.apache.vysper.storage.StorageProvider; +import org.apache.vysper.xmpp.addressing.Entity; + +/** + * @author Réda Housni Alaoui + */ +public interface MessageArchives extends StorageProvider { + + Optional<MessageArchive> retrieveUserMessageArchive(Entity userBareJid); + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java new file mode 100644 index 0000000..76cc182 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessagePageFilter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import java.util.Optional; +import java.util.OptionalLong; + +/** + * @author Réda Housni Alaoui + */ +public interface MessagePageFilter { + + Optional<Long> pageSize(); + + Optional<String> firstMessageId(); + + Optional<String> lastMessageId(); + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java new file mode 100644 index 0000000..c884b98 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessage.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import static java.util.Objects.requireNonNull; + +import java.time.ZonedDateTime; + +import org.apache.vysper.xmpp.stanza.MessageStanza; + +/** + * @author Réda Housni Alaoui + */ +public class SimpleArchivedMessage implements ArchivedMessage { + + private final String id; + + private final ZonedDateTime dateTime; + + private final MessageStanza stanza; + + public SimpleArchivedMessage(String id, ZonedDateTime dateTime, MessageStanza stanza) { + this.id = requireNonNull(id); + this.dateTime = requireNonNull(dateTime); + this.stanza = requireNonNull(stanza); + } + + @Override + public MessageStanza stanza() { + return stanza; + } + + @Override + public ZonedDateTime dateTime() { + return dateTime; + } + + @Override + public String id() { + return id; + } + +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/SimpleArchivedMessages.java new file mode 100644 index 0000000..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..32b3556 --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserArchiveQueryHandler.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user; + +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.vysper.xml.fragment.XMLSemanticError; +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.ArchiveQuery; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MAMQueryHandler; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.MatchingArchivedMessageResults; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.query.Query; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.ArchivedMessages; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.SessionContext; +import org.apache.vysper.xmpp.server.response.ServerErrorResponses; +import org.apache.vysper.xmpp.stanza.Stanza; +import org.apache.vysper.xmpp.stanza.StanzaErrorCondition; +import org.apache.vysper.xmpp.stanza.StanzaErrorType; + +/** + * @author Réda Housni Alaoui + */ +public class UserArchiveQueryHandler implements MAMQueryHandler { + + @Override + public boolean supports(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) { + return true; + } + + @Override + public List<Stanza> handle(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) { + try { + return doHandle(query, serverRuntimeContext, sessionContext); + } catch (XMLSemanticError xmlSemanticError) { + Stanza internalServerError = ServerErrorResponses.getStanzaError(StanzaErrorCondition.INTERNAL_SERVER_ERROR, + query.iqStanza(), StanzaErrorType.CANCEL, null, null, null); + return Collections.singletonList(internalServerError); + } + } + + private List<Stanza> doHandle(Query query, ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext) + throws XMLSemanticError { + Entity initiatingEntity = sessionContext.getInitiatingEntity(); + Entity archiveOwner = ofNullable(query.iqStanza().getTo()).orElse(initiatingEntity); + if (!sessionContext.getInitiatingEntity().getBareJID().equals(archiveOwner.getBareJID())) { + // The initiating user is trying to read the archive of another user + return Collections.singletonList(ServerErrorResponses.getStanzaError(StanzaErrorCondition.FORBIDDEN, + query.iqStanza(), StanzaErrorType.CANCEL, + "Entity " + initiatingEntity + " is not allowed to query " + archiveOwner + " message archives.", + null, null)); + } + + MessageArchives archives = requireNonNull( + (MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class), + "Could not find an instance of " + MessageArchives.class); + + Entity archiveId = archiveOwner.getBareJID(); + Optional<MessageArchive> archive = archives.retrieveUserMessageArchive(archiveId); + if (!archive.isPresent()) { + return Collections.singletonList(ServerErrorResponses.getStanzaError(StanzaErrorCondition.ITEM_NOT_FOUND, + query.iqStanza(), StanzaErrorType.CANCEL, "No user message archive found for entity " + archiveId, + null, null)); + } + + ArchivedMessages archivedMessages = new ArchiveQuery(archive.get(), archiveId, query).execute(); + return new MatchingArchivedMessageResults(initiatingEntity, archiveId, query, archivedMessages).toStanzas(); + } +} diff --git a/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java new file mode 100644 index 0000000..9dc1fbd --- /dev/null +++ b/server/extensions/xep0313-mam/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListener.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import org.apache.vysper.event.EventListener; +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent; +import org.apache.vysper.xmpp.modules.core.base.handler.XMPPCoreStanzaHandler; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SimpleMessage; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchive; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.SessionContext; +import org.apache.vysper.xmpp.stanza.MessageStanza; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Réda Housni Alaoui + */ +public class UserMessageListener implements EventListener<AcceptedMessageEvent> { + + private static final Logger LOG = LoggerFactory.getLogger(UserMessageListener.class); + + @Override + public void onEvent(AcceptedMessageEvent acceptedMessage) { + archive(acceptedMessage); + } + + private void archive(AcceptedMessageEvent acceptedMessage) { + ServerRuntimeContext serverRuntimeContext = acceptedMessage.serverRuntimeContext(); + MessageArchives archives = requireNonNull( + (MessageArchives) serverRuntimeContext.getStorageProvider(MessageArchives.class), + "Could not find an instance of " + MessageArchives.class); + + MessageStanza messageStanza = acceptedMessage.messageStanza(); + + Entity archiveJID; + if (acceptedMessage.isOutbound()) { + // We will store the message in the sender archive + SessionContext sessionContext = acceptedMessage.sessionContext(); + archiveJID = XMPPCoreStanzaHandler.extractSenderJID(messageStanza, sessionContext); + } else { + // We will store the message in the receiver archive + archiveJID = requireNonNull(messageStanza.getTo(), "No 'to' found in " + messageStanza); + } + + // Servers that expose archive messages of sent/received messages on behalf of + // local users MUST expose these archives to the user on the user's bare JID. + archiveJID = archiveJID.getBareJID(); + + Optional<MessageArchive> userArchive = archives.retrieveUserMessageArchive(archiveJID); + if (!userArchive.isPresent()) { + LOG.debug("No archive returned for user with bare JID '{}'", archiveJID); + return; + } + + userArchive.get().archive(new SimpleMessage(messageStanza)); + } +} diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java new file mode 100644 index 0000000..4de303a --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/MAMModuleTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.vysper.xmpp.modules.servicediscovery.management.Feature; +import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoElement; +import org.apache.vysper.xmpp.modules.servicediscovery.management.InfoRequest; +import org.apache.vysper.xmpp.modules.servicediscovery.management.ServerInfoRequestListener; +import org.apache.vysper.xmpp.modules.servicediscovery.management.ServiceDiscoveryRequestException; +import org.apache.vysper.xmpp.protocol.NamespaceURIs; +import org.junit.Test; + +import junit.framework.Assert; + +/** + * @author The Apache MINA Project (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..61ba7b8 --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/ServerRuntimeContextMock.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import static org.mockito.Mockito.mock; + +import java.util.List; + +import javax.net.ssl.SSLContext; + +import org.apache.vysper.event.EventBus; +import org.apache.vysper.storage.StorageProvider; +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.authentication.UserAuthentication; +import org.apache.vysper.xmpp.delivery.StanzaRelay; +import org.apache.vysper.xmpp.modules.Module; +import org.apache.vysper.xmpp.modules.ServerRuntimeContextService; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchives; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchivesMock; +import org.apache.vysper.xmpp.protocol.StanzaHandler; +import org.apache.vysper.xmpp.protocol.StanzaProcessor; +import org.apache.vysper.xmpp.server.ServerFeatures; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.components.Component; +import org.apache.vysper.xmpp.server.s2s.XMPPServerConnectorRegistry; +import org.apache.vysper.xmpp.stanza.Stanza; +import org.apache.vysper.xmpp.state.presence.LatestPresenceCache; +import org.apache.vysper.xmpp.state.resourcebinding.ResourceRegistry; + +/** + * @author Réda Housni Alaoui + */ +public class ServerRuntimeContextMock implements ServerRuntimeContext { + + private MessageArchivesMock userMessageArchives; + + public MessageArchivesMock givenUserMessageArchives() { + userMessageArchives = new MessageArchivesMock(); + return userMessageArchives; + } + + @Override + public StanzaHandler getHandler(Stanza stanza) { + throw new UnsupportedOperationException(); + } + + @Override + public String getNextSessionId() { + throw new UnsupportedOperationException(); + } + + @Override + public Entity getServerEnitity() { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultXMLLang() { + throw new UnsupportedOperationException(); + } + + @Override + public StanzaProcessor getStanzaProcessor() { + throw new UnsupportedOperationException(); + } + + @Override + public StanzaRelay getStanzaRelay() { + throw new UnsupportedOperationException(); + } + + @Override + public ServerFeatures getServerFeatures() { + throw new UnsupportedOperationException(); + } + + @Override + public SSLContext getSslContext() { + throw new UnsupportedOperationException(); + } + + @Override + public UserAuthentication getUserAuthentication() { + throw new UnsupportedOperationException(); + } + + @Override + public ResourceRegistry getResourceRegistry() { + return mock(ResourceRegistry.class); + } + + @Override + public LatestPresenceCache getPresenceCache() { + throw new UnsupportedOperationException(); + } + + @Override + public void registerServerRuntimeContextService(ServerRuntimeContextService service) { + throw new UnsupportedOperationException(); + } + + @Override + public ServerRuntimeContextService getServerRuntimeContextService(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public StorageProvider getStorageProvider(Class<? extends StorageProvider> clazz) { + if (MessageArchives.class.equals(clazz)) { + return userMessageArchives; + } + return null; + } + + @Override + public void registerComponent(Component component) { + throw new UnsupportedOperationException(); + } + + @Override + public StanzaProcessor getComponentStanzaProcessor(Entity entity) { + throw new UnsupportedOperationException(); + } + + @Override + public XMPPServerConnectorRegistry getServerConnectorRegistry() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Module> getModules() { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T getModule(Class<T> clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public void addModule(Module module) { + throw new UnsupportedOperationException(); + } + + @Override + public EventBus getEventBus() { + throw new UnsupportedOperationException(); + } +} diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java new file mode 100644 index 0000000..5b69042 --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/SessionContextMock.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.server.ServerRuntimeContext; +import org.apache.vysper.xmpp.server.SessionContext; +import org.apache.vysper.xmpp.server.SessionState; +import org.apache.vysper.xmpp.state.resourcebinding.BindException; +import org.apache.vysper.xmpp.writer.StanzaWriter; + +/** + * @author Réda Housni Alaoui + */ +public class SessionContextMock implements SessionContext { + + private ServerRuntimeContext serverRuntimeContext; + + private Entity initiatingEntity; + + public void givenServerRuntimeContext(ServerRuntimeContext serverRuntimeContext) { + this.serverRuntimeContext = serverRuntimeContext; + } + + public void givenInitiatingEntity(Entity initiatingEntity) { + this.initiatingEntity = initiatingEntity; + } + + @Override + public ServerRuntimeContext getServerRuntimeContext() { + return serverRuntimeContext; + } + + @Override + public boolean isRemotelyInitiatedSession() { + throw new UnsupportedOperationException(); + } + + @Override + public Entity getInitiatingEntity() { + return initiatingEntity; + } + + @Override + public void setInitiatingEntity(Entity entity) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isServerToServer() { + throw new UnsupportedOperationException(); + } + + @Override + public void setServerToServer() { + throw new UnsupportedOperationException(); + } + + @Override + public void setClientToServer() { + throw new UnsupportedOperationException(); + } + + @Override + public SessionState getState() { + throw new UnsupportedOperationException(); + } + + @Override + public String getSessionId() { + throw new UnsupportedOperationException(); + } + + @Override + public String getXMLLang() { + throw new UnsupportedOperationException(); + } + + @Override + public void setXMLLang(String languageCode) { + throw new UnsupportedOperationException(); + } + + @Override + public StanzaWriter getResponseWriter() { + throw new UnsupportedOperationException(); + } + + @Override + public void endSession(SessionTerminationCause terminationCause) { + throw new UnsupportedOperationException(); + } + + @Override + public Entity getServerJID() { + throw new UnsupportedOperationException(); + } + + @Override + public void switchToTLS(boolean delayed, boolean clientTls) { + throw new UnsupportedOperationException(); + } + + @Override + public void setIsReopeningXMLStream() { + throw new UnsupportedOperationException(); + } + + @Override + public String bindResource() throws BindException { + throw new UnsupportedOperationException(); + } + + @Override + public String nextSequenceValue() { + throw new UnsupportedOperationException(); + } + + @Override + public Object putAttribute(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(String key) { + throw new UnsupportedOperationException(); + } +} diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java new file mode 100644 index 0000000..bbf9369 --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/StanzaAssert.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.vysper.xmpp.modules.extension.xep0313_mam; + +import org.apache.vysper.xml.fragment.Renderer; +import org.apache.vysper.xmpp.stanza.Stanza; +import org.junit.Assert; + +public class StanzaAssert { + + public static void assertEquals(Stanza expected, Stanza actual) { + try { + Assert.assertEquals(expected, actual); + } catch(Throwable e) { + // print something useful + Assert.assertEquals(new Renderer(expected).getComplete(), new Renderer(actual).getComplete()); + } + } +} diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/query/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/spi/MessageArchivesMock.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchivesMock.java new file mode 100644 index 0000000..b484fe9 --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/spi/MessageArchivesMock.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.vysper.xmpp.addressing.Entity; + +/** + * @author Réda Housni Alaoui + */ +public class MessageArchivesMock implements MessageArchives { + + private final Map<Entity, MessageArchive> archiveById = new HashMap<>(); + + public MessageArchiveMock givenArchive(Entity archiveId) { + MessageArchiveMock userMessageArchiveMock = new MessageArchiveMock(); + archiveById.put(archiveId, userMessageArchiveMock); + return userMessageArchiveMock; + } + + @Override + public Optional<MessageArchive> retrieveUserMessageArchive(Entity userBareJid) { + return Optional.ofNullable(archiveById.get(userBareJid)); + } +} diff --git a/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java new file mode 100644 index 0000000..beebdaa --- /dev/null +++ b/server/extensions/xep0313-mam/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0313_mam/user/UserMessageListenerTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.vysper.xmpp.modules.extension.xep0313_mam.user; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.addressing.EntityImpl; +import org.apache.vysper.xmpp.modules.core.base.handler.AcceptedMessageEvent; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.ServerRuntimeContextMock; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.SessionContextMock; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchiveMock; +import org.apache.vysper.xmpp.modules.extension.xep0313_mam.spi.MessageArchivesMock; +import org.apache.vysper.xmpp.stanza.MessageStanza; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Réda Housni Alaoui + */ +public class UserMessageListenerTest { + + private static final Entity JULIET_IN_CHAMBER = EntityImpl.parseUnchecked("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(); + + MessageArchivesMock archives = serverRuntimeContext.givenUserMessageArchives(); + + julietArchive = archives.givenArchive(JULIET_IN_CHAMBER.getBareJID()); + romeoArchive = archives.givenArchive(ROMEO_IN_ORCHARD.getBareJID()); + macbethArchive = archives.givenArchive(MACBETH_IN_KITCHEN.getBareJID()); + + SessionContextMock sessionContext = new SessionContextMock(); + sessionContext.givenInitiatingEntity(INITIATING_ENTITY); + sessionContext.givenServerRuntimeContext(serverRuntimeContext); + + messageStanza = mock(MessageStanza.class); + acceptedMessage = mock(AcceptedMessageEvent.class); + when(acceptedMessage.serverRuntimeContext()).thenReturn(serverRuntimeContext); + when(acceptedMessage.sessionContext()).thenReturn(sessionContext); + when(acceptedMessage.messageStanza()).thenReturn(messageStanza); + + tested = new UserMessageListener(); + } + + @Test + public void outboundMessageHavingFrom() { + when(acceptedMessage.isOutbound()).thenReturn(true); + when(messageStanza.getFrom()).thenReturn(ROMEO_IN_ORCHARD); + + tested.onEvent(acceptedMessage); + + romeoArchive.assertUniqueArchivedMessageStanza(messageStanza); + } + + @Test + public void outboundMessageWithoutFrom() { + when(acceptedMessage.isOutbound()).thenReturn(true); + + tested.onEvent(acceptedMessage); + + julietArchive.assertUniqueArchivedMessageStanza(messageStanza); + } + + @Test + public void inboundMessage() { + when(acceptedMessage.isOutbound()).thenReturn(false); + when(messageStanza.getTo()).thenReturn(MACBETH_IN_KITCHEN); + + tested.onEvent(acceptedMessage); + + macbethArchive.assertUniqueArchivedMessageStanza(messageStanza); + } + + @Test + public void unexistingArchive() { + when(acceptedMessage.isOutbound()).thenReturn(true); + when(messageStanza.getFrom()).thenReturn(ALICE_IN_RABBIT_HOLE); + + tested.onEvent(acceptedMessage); + + julietArchive.assertEmpty(); + romeoArchive.assertEmpty(); + macbethArchive.assertEmpty(); + } + +} \ No newline at end of file