XEP-0059 Result Set Management (#11)
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementModule.java b/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementModule.java
new file mode 100644
index 0000000..eba188e
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementModule.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.xep0059_result_set_management;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.vysper.xmpp.modules.DefaultDiscoAwareModule;
+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.NamespaceURIs;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ResultSetManagementModule extends DefaultDiscoAwareModule implements ServerInfoRequestListener {
+ @Override
+ public String getName() {
+ return "XEP-0059 Result Set Management";
+ }
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public List<InfoElement> getServerInfosFor(InfoRequest request) {
+ if (StringUtils.isNotEmpty(request.getNode())) {
+ return null;
+ }
+
+ List<InfoElement> infoElements = new ArrayList<>();
+ infoElements.add(new Feature(NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT));
+ return infoElements;
+ }
+}
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
new file mode 100644
index 0000000..680ce68
--- /dev/null
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/Set.java
@@ -0,0 +1,240 @@
+/*
+ * 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.xep0059_result_set_management;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+
+import org.apache.vysper.xml.fragment.Attribute;
+import org.apache.vysper.xml.fragment.XMLElement;
+import org.apache.vysper.xml.fragment.XMLFragment;
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.apache.vysper.xml.fragment.XMLText;
+import org.apache.vysper.xmpp.protocol.NamespaceURIs;
+
+/**
+ * The Set element as defined by
+ * <a href="https://xmpp.org/extensions/xep-0059.html#schema">XEP-0059 Result
+ * Set Management</a>
+ *
+ * @author Réda Housni Alaoui
+ */
+public class Set {
+
+ private static final String ELEMENT_NAME = "set";
+
+ private static final String FIRST = "first";
+
+ private static final String INDEX = "index";
+
+ private final XMLElement element;
+
+ public Set(XMLElement element) {
+ if (!ELEMENT_NAME.equals(element.getName())) {
+ throw new IllegalArgumentException("ResultSetManagement element must be named '" + ELEMENT_NAME
+ + "' instead of '" + element.getName() + "'");
+ }
+ if (!NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT.equals(element.getNamespaceURI())) {
+ throw new IllegalArgumentException("ResultSetManagement element must be bound to namespace uri '"
+ + NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT + "' instead of '" + element.getNamespaceURI() + "'");
+ }
+ this.element = element;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Optional<String> getAfter() throws XMLSemanticError {
+ return getElementText("after");
+ }
+
+ public Optional<String> getBefore() throws XMLSemanticError {
+ return getElementText("before");
+ }
+
+ public OptionalInt getCount() throws XMLSemanticError {
+ return getElementInt("count");
+ }
+
+ public Optional<First> getFirst() throws XMLSemanticError {
+ return ofNullable(element.getSingleInnerElementsNamed(FIRST)).map(First::new);
+ }
+
+ public OptionalInt getIndex() throws XMLSemanticError {
+ return getElementInt(INDEX);
+ }
+
+ public Optional<String> getLast() throws XMLSemanticError {
+ return getElementText("last");
+ }
+
+ public OptionalInt getMax() throws XMLSemanticError {
+ return getElementInt("max");
+ }
+
+ private Optional<String> getElementText(String elementName) throws XMLSemanticError {
+ return ofNullable(element.getSingleInnerElementsNamed(elementName)).map(XMLElement::getInnerText)
+ .map(XMLText::getText);
+ }
+
+ private OptionalInt getElementInt(String elementName) throws XMLSemanticError {
+ return getElementText(elementName).map(Integer::parseInt).map(OptionalInt::of).orElse(OptionalInt.empty());
+ }
+
+ public static class First {
+
+ private final XMLElement element;
+
+ private First(XMLElement element) {
+ this.element = element;
+ }
+
+ private XMLElement element() {
+ return element;
+ }
+
+ public String getValue() {
+ return element.getInnerText().getText();
+ }
+
+ public OptionalInt getIndex() {
+ return ofNullable(element.getAttributeValue(INDEX)).map(Integer::parseInt).map(OptionalInt::of)
+ .orElse(OptionalInt.empty());
+ }
+
+ }
+
+ public static class Builder {
+
+ private String after;
+
+ private String before;
+
+ private Integer count;
+
+ private First first;
+
+ private Integer index;
+
+ private String last;
+
+ private Integer max;
+
+ private Builder() {
+
+ }
+
+ public Builder after(String after) {
+ this.after = after;
+ return this;
+ }
+
+ public Builder before(String before) {
+ this.before = before;
+ return this;
+ }
+
+ public Builder count(Integer count) {
+ this.count = count;
+ return this;
+ }
+
+ public FirstBuilder startFirst() {
+ return new FirstBuilder(this);
+ }
+
+ public Builder index(Integer index) {
+ this.index = index;
+ return this;
+ }
+
+ public Builder last(String last) {
+ this.last = last;
+ return this;
+ }
+
+ public Builder max(Integer max) {
+ this.max = max;
+ return this;
+ }
+
+ private XMLFragment createElement(String name, Object innerValue) {
+ return new XMLElement(null, name, null, Collections.emptyList(),
+ Collections.singletonList(new XMLText(String.valueOf(innerValue))));
+ }
+
+ public Set build() {
+ List<XMLFragment> innerFragments = new ArrayList<>();
+ ofNullable(after).map(innerValue -> createElement("after", innerValue)).ifPresent(innerFragments::add);
+ ofNullable(before).map(innerValue -> createElement("before", innerValue)).ifPresent(innerFragments::add);
+ ofNullable(count).map(innerValue -> createElement("count", innerValue)).ifPresent(innerFragments::add);
+ ofNullable(first).map(First::element).ifPresent(innerFragments::add);
+ ofNullable(index).map(innerValue -> createElement("index", innerValue)).ifPresent(innerFragments::add);
+ ofNullable(last).map(innerValue -> createElement("last", innerValue)).ifPresent(innerFragments::add);
+ ofNullable(max).map(innerValue -> createElement("max", innerValue)).ifPresent(innerFragments::add);
+
+ return new Set(new XMLElement(NamespaceURIs.XEP0059_RESULT_SET_MANAGEMENT, ELEMENT_NAME, null,
+ Collections.emptyList(), innerFragments));
+ }
+
+ }
+
+ public static class FirstBuilder {
+
+ private final Builder builder;
+
+ private String value;
+
+ private Integer index;
+
+ private FirstBuilder(Builder builder) {
+ this.builder = requireNonNull(builder);
+ }
+
+ public FirstBuilder value(String value) {
+ this.value = value;
+ return this;
+ }
+
+ public FirstBuilder index(Integer index) {
+ this.index = index;
+ return this;
+ }
+
+ public Builder endFirst() {
+ List<Attribute> attributes = new ArrayList<>();
+ ofNullable(index).map(value -> new Attribute(INDEX, String.valueOf(index))).ifPresent(attributes::add);
+ List<XMLFragment> innerFragments = new ArrayList<>();
+ ofNullable(value).map(XMLText::new).ifPresent(innerFragments::add);
+
+ builder.first = new First(new XMLElement(null, FIRST, null, attributes, innerFragments));
+ return builder;
+ }
+
+ }
+
+}
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 551a99e..56bc78c 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
@@ -109,4 +109,6 @@
public static final String XEP0124_BOSH = "http://jabber.org/protocol/httpbind";
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";
}
diff --git a/server/core/src/main/java/org/apache/vysper/xmpp/server/XMPPServer.java b/server/core/src/main/java/org/apache/vysper/xmpp/server/XMPPServer.java
index f22f66b..8f52c4e 100644
--- a/server/core/src/main/java/org/apache/vysper/xmpp/server/XMPPServer.java
+++ b/server/core/src/main/java/org/apache/vysper/xmpp/server/XMPPServer.java
@@ -42,6 +42,7 @@
import org.apache.vysper.xmpp.delivery.inbound.DeliveringExternalInboundStanzaRelay;
import org.apache.vysper.xmpp.delivery.inbound.DeliveringInternalInboundStanzaRelay;
import org.apache.vysper.xmpp.modules.Module;
+import org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management.ResultSetManagementModule;
import org.apache.vysper.xmpp.modules.extension.xep0160_offline_storage.OfflineStorageProvider;
import org.apache.vysper.xmpp.modules.roster.RosterModule;
import org.apache.vysper.xmpp.modules.servicediscovery.ServiceDiscoveryModule;
@@ -108,6 +109,7 @@
// add default modules
initialModules.add(new ServiceDiscoveryModule());
initialModules.add(new RosterModule());
+ initialModules.add(new ResultSetManagementModule());
}
public void setSASLMechanisms(List<SASLMechanism> validMechanisms) {
diff --git a/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java b/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
new file mode 100644
index 0000000..c52dfd6
--- /dev/null
+++ b/server/core/src/test/java/org/apache/vysper/xmpp/modules/extension/xep0059_result_set_management/ResultSetManagementTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.vysper.xmpp.modules.extension.xep0059_result_set_management;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.vysper.xml.fragment.XMLSemanticError;
+import org.junit.Test;
+
+/**
+ * @author Réda Housni Alaoui
+ */
+public class ResultSetManagementTest {
+
+ @Test
+ public void parse() throws XMLSemanticError {
+ Set tested = Set.builder().after("after-value").before("before-value").count(5)
+ .index(10).last("last-value").max(15).startFirst().index(20).value("first-value").endFirst().build();
+
+ assertEquals("after-value", tested.getAfter().get());
+ assertEquals("before-value", tested.getBefore().get());
+ assertEquals(5, tested.getCount().getAsInt());
+ assertEquals(10, tested.getIndex().getAsInt());
+ assertEquals("last-value", tested.getLast().get());
+ assertEquals(15, tested.getMax().getAsInt());
+
+ Set.First first = tested.getFirst().get();
+ assertEquals("first-value", first.getValue());
+ assertEquals(20, first.getIndex().getAsInt());
+ }
+
+}
\ No newline at end of file