Add support for XEP-0372: References

This commit is contained in:
Paul Schaub 2018-02-04 23:08:52 +01:00
parent 1bd3469fec
commit 32b3212a9b
10 changed files with 495 additions and 0 deletions

View File

@ -94,6 +94,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| Stable and Unique Stanza IDs | [XEP-0359](http://xmpp.org/extensions/xep-0359.html) | This specification describes unique and stable IDs for messages. |
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| References | [XEP-0372](http://xmpp.org/extensions/xep-0363.html) | Add references like mentions or external data to stanzas. |
| [Spoiler Messages](spoiler.md) | [XEP-0382](http://xmpp.org/extensions/xep-0382.html) | Indicate that the body of a message should be treated as a spoiler |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |

View File

@ -0,0 +1,19 @@
References
==========
[Back](index.md)
References are a way to refer to other entities like users, other messages or external data from within a message.
Typical use-cases are mentioning other users by name, but referencing to their BareJid, or linking to a sent file.
## Usage
Mention a user and link to their bare jid.
```
Message message = new Message("Alice is a realy nice person.");
BareJid alice = JidCreate.bareFrom("alice@capulet.lit");
ReferenceManager.addMention(message, 0, 5, alice);
```
TODO: Add more use cases (for example for MIX, SIMS...)

View File

@ -0,0 +1,109 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.reference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.reference.element.ReferenceElement;
import org.jxmpp.jid.BareJid;
public final class ReferenceManager extends Manager {
public static final String NAMESPACE = "urn:xmpp:reference:0";
private static final Map<XMPPConnection, ReferenceManager> INSTANCES = new WeakHashMap<>();
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
private ReferenceManager(XMPPConnection connection) {
super(connection);
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
}
/**
* Return a new instance of the ReferenceManager for the given connection.
*
* @param connection xmpp connection
* @return reference manager instance
*/
public static ReferenceManager getInstanceFor(XMPPConnection connection) {
ReferenceManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new ReferenceManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
/**
* Add a reference to another users bare jid to a stanza.
*
* @param stanza stanza.
* @param begin start index of the mention in the messages body.
* @param end end index of the mention in the messages body.
* @param jid referenced jid.
*/
public static void addMention(Stanza stanza, int begin, int end, BareJid jid) {
ReferenceElement reference = new ReferenceElement(begin, end, ReferenceElement.Type.mention, null,
"xmpp:" + jid.toString());
stanza.addExtension(reference);
}
/**
* Return a list of all reference extensions contained in a stanza.
* If there are no reference elements, return an empty list.
*
* @param stanza stanza
* @return list of all references contained in the stanza
*/
public static List<ReferenceElement> getReferencesFromStanza(Stanza stanza) {
List<ReferenceElement> references = new ArrayList<>();
List<ExtensionElement> extensions = stanza.getExtensions(ReferenceElement.ELEMENT, NAMESPACE);
for (ExtensionElement e : extensions) {
references.add((ReferenceElement) e);
}
return references;
}
/**
* Return true, if the stanza contains at least one reference extension.
*
* @param stanza stanza
* @return true if stanza contains references
*/
public static boolean containsReferences(Stanza stanza) {
return getReferencesFromStanza(stanza).size() > 0;
}
}

View File

@ -0,0 +1,141 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.reference.element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.reference.ReferenceManager;
public class ReferenceElement implements ExtensionElement {
public static final String ELEMENT = "reference";
public static final String ATTR_BEGIN = "begin";
public static final String ATTR_END = "end";
public static final String ATTR_TYPE = "type";
public static final String ATTR_ANCHOR = "anchor";
public static final String ATTR_URI = "uri";
public enum Type {
mention,
data
}
private final Integer begin;
private final Integer end;
private final Type type;
private final String anchor;
private final String uri;
// Non-XEP-compliant, but needed for SIMS
private final ExtensionElement child;
/**
* XEP-incompliant (v0.2) constructor. This is needed for SIMS.
*
* @param begin
* @param end
* @param type
* @param anchor
* @param uri
* @param child
*/
public ReferenceElement(Integer begin, Integer end, Type type, String anchor, String uri, ExtensionElement child) {
if (begin != null && begin < 0) {
throw new IllegalArgumentException("Attribute 'begin' MUST NOT be smaller than 0.");
}
if (end != null && end < 0) {
throw new IllegalArgumentException("Attribute 'end' MUST NOT be smaller than 0.");
}
if (begin != null && end != null && begin >= end) {
throw new IllegalArgumentException("Attribute 'begin' MUST be smaller than attribute 'end'.");
}
if (type == null) {
throw new NullPointerException("Attribute 'type' MUST NOT be null.");
}
// TODO: The uri attribute is not mandatory according to SIMS, but it is according to references.
/*if (uri == null) {
throw new NullPointerException("Attribute 'uri' MUST NOT be null.");
}*/
this.begin = begin;
this.end = end;
this.type = type;
this.anchor = anchor;
this.uri = uri;
this.child = child;
}
/**
* XEP-Compliant constructor.
*
* @param begin
* @param end
* @param type
* @param anchor
* @param uri
*/
public ReferenceElement(Integer begin, Integer end, Type type, String anchor, String uri) {
this(begin, end, type, anchor, uri, null);
}
public Integer getBegin() {
return begin;
}
public Integer getEnd() {
return end;
}
public Type getType() {
return type;
}
public String getAnchor() {
return anchor;
}
public String getUri() {
return uri;
}
@Override
public String getNamespace() {
return ReferenceManager.NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this)
.optIntAttribute(ATTR_BEGIN, begin != null ? begin : -1)
.optIntAttribute(ATTR_END, end != null ? end : -1)
.attribute(ATTR_TYPE, type.toString())
.optAttribute(ATTR_ANCHOR, anchor)
.optAttribute(ATTR_URI, uri);
if (child == null) {
return xml.closeEmptyElement();
} else {
return xml.rightAngleBracket()
.append(child.toXML())
.closeElement(this);
}
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.
*/
/**
* Smack's API for XEP-0372: References.
*/
package org.jivesoftware.smackx.reference.element;

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.
*/
/**
* Smack's API for XEP-0372: References.
*/
package org.jivesoftware.smackx.reference;

View File

@ -0,0 +1,55 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.reference.provider;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smackx.reference.element.ReferenceElement;
import org.xmlpull.v1.XmlPullParser;
public class ReferenceProvider extends ExtensionElementProvider<ReferenceElement> {
public static final ReferenceProvider TEST_PROVIDER = new ReferenceProvider();
@Override
public ReferenceElement parse(XmlPullParser parser, int initialDepth) throws Exception {
Integer begin = ParserUtils.getIntegerAttribute(parser, ReferenceElement.ATTR_BEGIN);
Integer end = ParserUtils.getIntegerAttribute(parser, ReferenceElement.ATTR_END);
String typeString = parser.getAttributeValue(null, ReferenceElement.ATTR_TYPE);
ReferenceElement.Type type = ReferenceElement.Type.valueOf(typeString);
String anchor = parser.getAttributeValue(null, ReferenceElement.ATTR_ANCHOR);
String uri = parser.getAttributeValue(null, ReferenceElement.ATTR_URI);
ExtensionElement child = null;
while (true) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
ExtensionElementProvider<?> provider = ProviderManager.getExtensionProvider(elementName, namespace);
if (provider != null) {
child = provider.parse(parser);
}
}
if (eventType == XmlPullParser.END_TAG) {
return new ReferenceElement(begin, end, type, anchor, uri, child);
}
}
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.
*/
/**
* Smack's API for XEP-0372: References.
*/
package org.jivesoftware.smackx.reference.provider;

View File

@ -289,6 +289,13 @@
<className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className>
</extensionProvider>
<!-- XEP-0372: References -->
<extensionProvider>
<elementName>reference</elementName>
<namespace>urn:xmpp:reference:0</namespace>
<className>org.jivesoftware.smackx.reference.provider.ReferenceProvider</className>
</extensionProvider>
<!-- XEP-0380: Explicit Message Encryption -->
<extensionProvider>
<elementName>encryption</elementName>

View File

@ -0,0 +1,103 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.reference;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNull;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.reference.element.ReferenceElement;
import org.jivesoftware.smackx.reference.provider.ReferenceProvider;
import org.junit.Test;
public class ReferenceTest extends SmackTestSuite {
@Test
public void providerMentionTest() throws Exception {
String xml = "<reference xmlns='urn:xmpp:reference:0' " +
"begin='72' " +
"end='78' " +
"type='mention' " +
"uri='xmpp:juliet@capulet.lit' />";
ReferenceElement element = new ReferenceElement(72, 78, ReferenceElement.Type.mention, null,
"xmpp:juliet@capulet.lit");
assertXMLEqual(xml, element.toXML().toString());
assertEquals(72, (int) element.getBegin());
assertEquals(78, (int) element.getEnd());
assertEquals(ReferenceElement.Type.mention, element.getType());
assertNull(element.getAnchor());
assertEquals("xmpp:juliet@capulet.lit", element.getUri());
ReferenceElement parsed = ReferenceProvider.TEST_PROVIDER.parse(TestUtils.getParser(xml));
assertXMLEqual(xml, parsed.toXML().toString());
}
/**
* TODO: The uri might not be following the XMPP schema.
* That shouldn't matter though.
* @throws Exception
*/
@Test
public void providerDataTest() throws Exception {
String xml = "<reference xmlns='urn:xmpp:reference:0' " +
"type='data' " +
"uri='xmpp:fdp.shakespeare.lit?;node=fdp/submitted/stan.isode.net/accidentreport;item=ndina872be' />";
ReferenceElement element = new ReferenceElement(null, null, ReferenceElement.Type.data, null,
"xmpp:fdp.shakespeare.lit?;node=fdp/submitted/stan.isode.net/accidentreport;item=ndina872be");
assertXMLEqual(xml, element.toXML().toString());
assertNull(element.getBegin());
assertNull(element.getEnd());
assertNull(element.getAnchor());
assertEquals(ReferenceElement.Type.data, element.getType());
assertEquals("xmpp:fdp.shakespeare.lit?;node=fdp/submitted/stan.isode.net/accidentreport;item=ndina872be", element.getUri());
ReferenceElement parsed = ReferenceProvider.TEST_PROVIDER.parse(TestUtils.getParser(xml));
assertXMLEqual(xml, parsed.toXML().toString());
}
@Test(expected = IllegalArgumentException.class)
public void beginGreaterEndIllegalTest() {
new ReferenceElement(100, 10, ReferenceElement.Type.mention, null, "test@test.test");
}
@Test(expected = IllegalArgumentException.class)
public void beginSmallerZeroTest() {
new ReferenceElement(-1, 12, ReferenceElement.Type.data, null, "test@test.test");
}
@Test(expected = IllegalArgumentException.class)
public void endSmallerZeroTest() {
new ReferenceElement(12, -2, ReferenceElement.Type.mention, null, "test@test.test");
}
@Test(expected = NullPointerException.class)
public void typeArgumentNullTest() {
new ReferenceElement(1, 2, null, null, "test@test.test");
}
/*
* TODO: Later maybe remove this test in case the uri attribute becomes optional.
@Test(expected = NullPointerException.class)
public void uriArgumentNullTest() {
new ReferenceElement(1, 2, ReferenceElement.Type.mention, null, null);
}
*/
}