From 2dc93d7639d4a351e16ecb3a04725902d08809c7 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 13 Sep 2014 11:03:40 +0200 Subject: [PATCH] Add support for XEP-0059: Result Set Management SMACK-581 --- documentation/extensions/index.md | 1 + .../org/jivesoftware/smack/packet/Packet.java | 11 +- .../jivesoftware/smack/util/PacketUtil.java | 38 +++++ .../jivesoftware/smack/util/ParserUtils.java | 6 + .../smack/util/XmlStringBuilder.java | 7 + .../pubsub/provider/PubSubProvider.java | 2 +- .../jivesoftware/smackx/rsm/RSMManager.java | 57 +++++++ .../smackx/rsm/packet/RSMSet.java | 145 ++++++++++++++++++ .../smackx/rsm/provider/RSMSetProvider.java | 82 ++++++++++ .../extensions.providers | 7 + .../rsm/provider/RSMSetProviderTest.java | 61 ++++++++ 11 files changed, 407 insertions(+), 10 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/RSMManager.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/packet/RSMSet.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/provider/RSMSetProvider.java create mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/rsm/provider/RSMSetProviderTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index f0816bf66..a65ebe861 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -36,6 +36,7 @@ Smack Extensions and currently supported XEPs by Smack (smack-extensions) | Ad-Hoc Commands | [XEP-0050](http://xmpp.org/extensions/xep-0049.html) | Advertising and executing application-specific commands. | | vcard-temp | [XEP-0054](http://xmpp.org/extensions/xep-0049.html) | The vCard-XML format currently in use. | | Jabber Search | [XEP-0055](http://xmpp.org/extensions/xep-0055.html) | Search information repositories on the XMPP network. | +| Result Set Management | [XEP-0059](http://xmpp.org/extensions/xep-0059.html) | Page through and otherwise manage the receipt of large result sets | | [PubSub](pubsub.html) | [XEP-0060](http://xmpp.org/extensions/xep-0060.html) | Generic publish and subscribe functionality. | | SOCKS5 Bytestrams | [XEP-0065](http://xmpp.org/extensions/xep-0065.html) | Out-of-band bytestream between any two XMPP entities. | | [XHTML-IM](xhtml.html) | [XEP-0071](http://xmpp.org/extensions/xep-0071.html) | Allows send and receiving formatted messages using XHTML. | diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Packet.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Packet.java index 4311dcf32..3a88e0a16 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Packet.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Packet.java @@ -17,6 +17,7 @@ package org.jivesoftware.smack.packet; +import org.jivesoftware.smack.util.PacketUtil; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -232,19 +233,11 @@ public abstract class Packet extends TopLevelStreamElement { * @param namespace the XML element namespace of the packet extension. * @return the extension, or null if it doesn't exist. */ - @SuppressWarnings("unchecked") public PE getExtension(String elementName, String namespace) { if (namespace == null) { return null; } - for (PacketExtension ext : packetExtensions) { - if ((elementName == null || elementName.equals(ext.getElementName())) - && namespace.equals(ext.getNamespace())) - { - return (PE) ext; - } - } - return null; + return PacketUtil.packetExtensionfromCollection(packetExtensions, elementName, namespace); } /** diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java new file mode 100644 index 000000000..854d30d34 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java @@ -0,0 +1,38 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * Licensed 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.jivesoftware.smack.util; + +import java.util.Collection; + +import org.jivesoftware.smack.packet.PacketExtension; + +public class PacketUtil { + + @SuppressWarnings("unchecked") + public static PE packetExtensionfromCollection( + Collection collection, String element, + String namespace) { + for (PacketExtension packetExtension : collection) { + if ((element == null || packetExtension.getElementName().equals( + element)) + && packetExtension.getNamespace().equals(namespace)) { + return (PE) packetExtension; + } + } + return null; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java index b804c7031..0264abbda 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java @@ -16,6 +16,7 @@ */ package org.jivesoftware.smack.util; +import java.io.IOException; import java.util.Locale; import org.xmlpull.v1.XmlPullParser; @@ -77,6 +78,11 @@ public class ParserUtils { } } + public static int getIntegerFromNextText(XmlPullParser parser) throws XmlPullParserException, IOException { + String intString = parser.nextText(); + return Integer.valueOf(intString); + } + public static Long getLongAttribute(XmlPullParser parser, String name) { String valueString = parser.getAttributeValue("", name); if (valueString == null) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 88227e67a..aed38f9cc 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -86,6 +86,13 @@ public class XmlStringBuilder implements Appendable, CharSequence { return this; } + public XmlStringBuilder optIntElement(String name, int value) { + if (value >= 0) { + element(name, String.valueOf(value)); + } + return this; + } + public XmlStringBuilder halfOpenElement(String name) { sb.append('<').append(name); return this; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java index b4b565703..b667729a0 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java @@ -45,7 +45,7 @@ public class PubSubProvider implements IQProvider if (eventType == XmlPullParser.START_TAG) { - PacketExtension ext = PacketParserUtils.parsePacketExtension(parser.getName(), namespace, parser); + PacketExtension ext = PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser); if (ext != null) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/RSMManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/RSMManager.java new file mode 100644 index 000000000..afbd614f7 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/RSMManager.java @@ -0,0 +1,57 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * Licensed 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.jivesoftware.smackx.rsm; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.PacketUtil; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.jivesoftware.smackx.rsm.packet.RSMSet.PageDirection; + +public class RSMManager { + + Collection page(int max) { + List packetExtensions = new LinkedList(); + packetExtensions.add(new RSMSet(max)); + return packetExtensions; + } + + Collection contiunePage(int max, Collection returnedExtensions) { + return continuePage(max, returnedExtensions, null); + } + + Collection continuePage(int max, + Collection returnedExtensions, + Collection additionalExtensions) { + if (returnedExtensions == null) { + throw new IllegalArgumentException("returnedExtensions must no be null"); + } + if (additionalExtensions == null) { + additionalExtensions = new LinkedList(); + } + RSMSet resultRsmSet = PacketUtil.packetExtensionfromCollection(returnedExtensions, RSMSet.ELEMENT, RSMSet.NAMESPACE); + if (resultRsmSet == null) { + throw new IllegalArgumentException("returnedExtensions did not contain a RSMset"); + } + RSMSet continePageRsmSet = new RSMSet(max, resultRsmSet.getLast(), PageDirection.after); + additionalExtensions.add(continePageRsmSet); + return additionalExtensions; + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/packet/RSMSet.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/packet/RSMSet.java new file mode 100644 index 000000000..8c9f88c18 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/packet/RSMSet.java @@ -0,0 +1,145 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * Licensed 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.jivesoftware.smackx.rsm.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class RSMSet implements PacketExtension { + + public static final String ELEMENT = "set"; + public static final String NAMESPACE = "http://jabber.org/protocol/rsm"; + + private final String after; + private final String before; + private final int count; + private final int index; + private final String last; + private final int max; + private final String firstString; + private final int firstIndex; + + public static enum PageDirection { + before, + after; + } + + public RSMSet(int max) { + this(max, -1); + } + + public RSMSet(int max, int index) { + this(null, null, -1, index, null, max, null, -1); + } + + public RSMSet(int max, String item, PageDirection pageDirection) { + switch (pageDirection) { + case before: + this.before = item; + this.after = null; + break; + case after: + this.before = null; + this.after = item; + break; + default: + throw new AssertionError(); + } + this.count = -1; + this.index = -1; + this.last = null; + this.max = max; + this.firstString = null; + this.firstIndex = -1; + } + + public RSMSet(String after, String before, int count, int index, + String last, int max, String firstString, int firstIndex) { + this.after = after; + this.before = before; + this.count = count; + this.index = index; + this.last = last; + this.max = max; + this.firstString = firstString; + this.firstIndex = firstIndex; + } + + public String getAfter() { + return after; + } + + public String getBefore() { + return before; + } + + public int getCount() { + return count; + } + + public int getIndex() { + return index; + } + + public String getLast() { + return last; + } + + public int getMax() { + return max; + } + + public String getFirst() { + return firstString; + } + + public int getFirstIndex() { + return firstIndex; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.rightAngleBracket(); + xml.optElement("after", after); + xml.optElement("before", before); + xml.optIntElement("count", count); + if (firstString != null) { + xml.halfOpenElement("first"); + xml.optIntAttribute("index", firstIndex); + xml.rightAngleBracket(); + xml.append(firstString); + xml.closeElement("first"); + } + xml.optIntElement("index", index); + xml.optElement("last", last); + xml.optIntElement("max", max); + xml.closeElement(this); + return xml; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/provider/RSMSetProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/provider/RSMSetProvider.java new file mode 100644 index 000000000..1a57f33bd --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/rsm/provider/RSMSetProvider.java @@ -0,0 +1,82 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * Licensed 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.jivesoftware.smackx.rsm.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.xmlpull.v1.XmlPullParser; + +public class RSMSetProvider implements PacketExtensionProvider { + + @Override + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + int initialDepth = parser.getDepth(); + + String after = null; + String before = null; + int count = -1; + int index = -1; + String last = null; + int max = -1; + String firstString = null; + int firstIndex = -1; + + outerloop: while (true) { + int event = parser.next(); + switch (event) { + case XmlPullParser.START_TAG: + String name = parser.getName(); + switch (name) { + case "after": + after = parser.nextText(); + break; + case "before": + before = parser.nextText(); + break; + case "count": + count = ParserUtils.getIntegerFromNextText(parser); + break; + case "first": + firstIndex = ParserUtils.getIntegerAttribute(parser, + "index", -1); + firstString = parser.nextText(); + break; + case "index": + index = ParserUtils.getIntegerFromNextText(parser); + break; + case "last": + last = parser.nextText(); + break; + case "max": + max = ParserUtils.getIntegerFromNextText(parser); + break; + } + break; + case XmlPullParser.END_TAG: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + } + } + return new RSMSet(after, before, count, index, last, max, firstString, + firstIndex); + } + +} diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers b/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers index 1eb277f5e..2d4d08994 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers +++ b/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers @@ -474,4 +474,11 @@ http://jabber.org/features/iq-register org.jivesoftware.smackx.iqregister.provider.RegistrationStreamFeatureProvider + + + + set + http://jabber.org/protocol/rsm + org.jivesoftware.smackx.rsm.provider.RSMSetProvider + diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/rsm/provider/RSMSetProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/rsm/provider/RSMSetProviderTest.java new file mode 100644 index 000000000..417cacf0f --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/rsm/provider/RSMSetProviderTest.java @@ -0,0 +1,61 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * Licensed 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.jivesoftware.smackx.rsm.provider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.InitExtensions; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.junit.Test; + +public class RSMSetProviderTest extends InitExtensions { + + @Test + public void testRsmSetProvider() throws Exception { + // @formatter:off + final String rsmset = + "" + + "" + + "" + + "aftervalue" + + "beforevalue" + + "1" + + "foo@bar.com" + + "3" + + "lastvalue" + + "4" + + "" + + "" + + ""; + // @formatter:on + + IQ iqWithRsm = (IQ) PacketParserUtils.parseStanza(rsmset); + RSMSet rsm = (RSMSet) iqWithRsm.getExtension(RSMSet.ELEMENT, RSMSet.NAMESPACE); + assertNotNull(rsm); + assertEquals("aftervalue", rsm.getAfter()); + assertEquals("beforevalue", rsm.getBefore()); + assertEquals(1, rsm.getCount()); + assertEquals(2, rsm.getFirstIndex()); + assertEquals("foo@bar.com", rsm.getFirst()); + assertEquals(3, rsm.getIndex()); + assertEquals("lastvalue", rsm.getLast()); + assertEquals(4, rsm.getMax()); + } +}