mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-02-18 14:49:23 +01:00
Compare commits
57 commits
32ae0d8826
...
498dde2d86
Author | SHA1 | Date | |
---|---|---|---|
|
498dde2d86 | ||
|
77e26fc575 | ||
|
25a5261dc0 | ||
|
577c59484b | ||
|
3270c113c5 | ||
|
2c39dff653 | ||
|
f61ecb65e7 | ||
|
e79710840b | ||
|
5e921e6393 | ||
|
dc443bccd4 | ||
|
cdc5396f6c | ||
|
661b2743d9 | ||
|
e58e6fa75d | ||
|
8e1003723e | ||
|
d0347d1e00 | ||
|
c07f41c119 | ||
|
5c5dfedce2 | ||
|
72b9f79692 | ||
|
22cff274bb | ||
|
c3c14cfdb9 | ||
|
0f7b7df1f0 | ||
|
20aaef2628 | ||
|
26ab832452 | ||
|
ab2822be3e | ||
|
c519dd1213 | ||
|
e2e228fc93 | ||
|
fa643f12d5 | ||
|
162651821e | ||
|
da5f59a996 | ||
|
77d12d4758 | ||
|
cda764d62d | ||
|
4f3d89e666 | ||
|
38c77fd573 | ||
|
cbc2024875 | ||
|
7f2e8b793a | ||
|
ad13effe41 | ||
|
f3e93cef32 | ||
|
dd248adb28 | ||
|
6c3cd53567 | ||
|
7f027bd339 | ||
|
2c6f444bab | ||
|
50da46ffda | ||
|
5114f6dfa4 | ||
|
fb7054bbe7 | ||
|
988954a9db | ||
|
aea95d3401 | ||
|
c49999b933 | ||
|
4f609b855c | ||
|
f5c412a98f | ||
905d5dc102 | |||
e0f7ddf5a8 | |||
72a9cb65a6 | |||
|
9b20e2efd8 | ||
|
6d9936a0a6 | ||
|
340bcb2d12 | ||
|
7c2f9e3603 | ||
|
d0a393118c |
155 changed files with 5449 additions and 3411 deletions
|
@ -4,8 +4,7 @@ android:
|
|||
components:
|
||||
- android-19
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- openjdk9
|
||||
- openjdk8
|
||||
- openjdk11
|
||||
|
||||
before_cache:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Smack
|
||||
=====
|
||||
|
||||
[![Build Status](https://travis-ci.org/igniterealtime/Smack.svg?branch=master)](https://travis-ci.org/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
|
||||
[![Build Status](https://api.travis-ci.com/igniterealtime/Smack.svg?branch=master)](https://travis-ci.com/github/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
|
||||
|
||||
About
|
||||
-----
|
||||
|
|
49
build.gradle
49
build.gradle
|
@ -101,20 +101,6 @@ allprojects {
|
|||
':smack-omemo-signal-integration-test',
|
||||
':smack-repl'
|
||||
].collect{ project(it) }
|
||||
// When this list is empty, then move the according javadoc
|
||||
// tool Werror option into the global configure section.
|
||||
nonStrictJavadocProjects = [
|
||||
':smack-bosh',
|
||||
':smack-core',
|
||||
':smack-experimental',
|
||||
':smack-extensions',
|
||||
':smack-im',
|
||||
':smack-integration-test',
|
||||
':smack-jingle-old',
|
||||
':smack-legacy',
|
||||
':smack-omemo',
|
||||
':smack-tcp',
|
||||
].collect{ project(it) }
|
||||
// Lazily evaluate the Android bootClasspath and offline
|
||||
// Javadoc using a closure, so that targets which do not
|
||||
// require it are still able to succeed without an Android
|
||||
|
@ -136,9 +122,11 @@ allprojects {
|
|||
// Default to true
|
||||
useSonatype = true
|
||||
}
|
||||
javaCompatilibity = JavaVersion.VERSION_1_8
|
||||
javaMajor = javaCompatilibity.getMajorVersion()
|
||||
}
|
||||
group = 'org.igniterealtime.smack'
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = javaCompatilibity
|
||||
targetCompatibility = sourceCompatibility
|
||||
version = shortVersion
|
||||
if (isSnapshot) {
|
||||
|
@ -258,7 +246,19 @@ allprojects {
|
|||
options.addStringOption('Xwerror', '-quiet')
|
||||
}
|
||||
}
|
||||
tasks.withType(Javadoc) {
|
||||
|
||||
if (JavaVersion.current().isJava9Compatible()) {
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption('-release', javaMajor)
|
||||
}
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.addAll([
|
||||
'--release', javaMajor,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc) {
|
||||
options.charSet = "UTF-8"
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
@ -304,16 +304,10 @@ task javadocAll(type: Javadoc) {
|
|||
project.sourceSets.main.compileClasspath})
|
||||
classpath += files(androidBootClasspath)
|
||||
options {
|
||||
// Add source compatiblitiy statement to work around bug in JDK 11
|
||||
// See
|
||||
// - https://bugs.openjdk.java.net/browse/JDK-8217177
|
||||
// - http://hg.openjdk.java.net/jdk/jdk/rev/8ce4083fc831
|
||||
// - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=920020
|
||||
source = sourceCompatibility
|
||||
linkSource = true
|
||||
use = true
|
||||
links = [
|
||||
"https://docs.oracle.com/javase/${sourceCompatibility.getMajorVersion()}/docs/api/",
|
||||
"https://docs.oracle.com/javase/${javaMajor}/docs/api/",
|
||||
"https://jxmpp.org/releases/$jxmppVersion/javadoc/",
|
||||
"https://minidns.org/releases/$miniDnsVersion/javadoc/",
|
||||
] as String[]
|
||||
|
@ -571,15 +565,6 @@ subprojects*.jar {
|
|||
}
|
||||
}
|
||||
|
||||
configure(subprojects - nonStrictJavadocProjects) {
|
||||
tasks.withType(Javadoc) {
|
||||
// Abort on javadoc warnings.
|
||||
// See JDK-8200363 (https://bugs.openjdk.java.net/browse/JDK-8200363)
|
||||
// for information about the -Xwerror option.
|
||||
options.addStringOption('Xwerror', '-quiet')
|
||||
}
|
||||
}
|
||||
|
||||
configure(subprojects - gplLicensedProjects) {
|
||||
checkstyle {
|
||||
configProperties.checkstyleLicenseHeader = "header"
|
||||
|
|
|
@ -102,7 +102,7 @@ The base class that integration tests need to subclass.
|
|||
|
||||
### `AbstractSmackLowLevelIntegrationTest`
|
||||
|
||||
Allows low level integration test, i.e. ever test method will have its on exclusive XMPPTCPConnection instances.
|
||||
Allows low level integration test, i.e. every test method will have its own exclusive XMPPTCPConnection instances.
|
||||
|
||||
### `AbstractSmackSpecificLowLevelIntegrationTest`
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ Smack Extensions and currently supported XEPs of smack-extensions
|
|||
| Result Set Management | [XEP-0059](https://xmpp.org/extensions/xep-0059.html) | n/a | Page through and otherwise manage the receipt of large result sets |
|
||||
| [PubSub](pubsub.md) | [XEP-0060](https://xmpp.org/extensions/xep-0060.html) | n/a | Generic publish and subscribe functionality. |
|
||||
| SOCKS5 Bytestreams | [XEP-0065](https://xmpp.org/extensions/xep-0065.html) | n/a | Out-of-band bytestream between any two XMPP entities. |
|
||||
| Field Standardization for Data Forms | [XEP-0068](https://xmpp.org/extensions/xep-0068.html) | n/a | Standardized field variables used in the context of jabber:x:data forms. |
|
||||
| [XHTML-IM](xhtml.md) | [XEP-0071](https://xmpp.org/extensions/xep-0071.html) | n/a | Allows send and receiving formatted messages using XHTML. |
|
||||
| In-Band Registration | [XEP-0077](https://xmpp.org/extensions/xep-0077.html) | n/a | In-band registration with XMPP services. |
|
||||
| Advanced Message Processing | [XEP-0079](https://xmpp.org/extensions/xep-0079.html) | n/a | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. |
|
||||
|
@ -120,6 +121,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
|||
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.6.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
|
||||
| [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. |
|
||||
| DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. |
|
||||
| Message Fastening | [XEP-0422](https://xmpp.org/extensions/xep-0422.html) | 0.1.1 | Mark payloads on a message to be logistically fastened to a previous message. |
|
||||
|
||||
Unofficial XMPP Extensions
|
||||
--------------------------
|
||||
|
|
|
@ -527,7 +527,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
saslFeatureReceived.init();
|
||||
lastFeaturesReceived.init();
|
||||
tlsHandled.init();
|
||||
closingStreamReceived.init();
|
||||
// TODO: We do not init() closingStreamReceived here, as the integration tests use it to check if we waited for
|
||||
// it.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -549,6 +550,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
|
||||
// Reset the connection state
|
||||
initState();
|
||||
closingStreamReceived.init();
|
||||
streamId = null;
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.filter;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.XmppElementUtil;
|
||||
|
||||
public class ExtensionElementFilter<E extends ExtensionElement> implements StanzaFilter {
|
||||
|
||||
private final Class<E> extensionElementClass;
|
||||
private final QName extensionElementQName;
|
||||
|
||||
protected ExtensionElementFilter(Class<E> extensionElementClass) {
|
||||
this.extensionElementClass = extensionElementClass;
|
||||
extensionElementQName = XmppElementUtil.getQNameFor(extensionElementClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean accept(Stanza stanza) {
|
||||
ExtensionElement extensionElement = stanza.getExtension(extensionElementQName);
|
||||
if (extensionElement == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!extensionElementClass.isInstance(extensionElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
E specificExtensionElement = extensionElementClass.cast(extensionElement);
|
||||
return accept(specificExtensionElement);
|
||||
}
|
||||
|
||||
public boolean accept(E extensionElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ public final class MessageTypeFilter extends FlexibleStanzaTypeFilter<Message> {
|
|||
public static final StanzaFilter HEADLINE = new MessageTypeFilter(Type.headline);
|
||||
public static final StanzaFilter ERROR = new MessageTypeFilter(Type.error);
|
||||
public static final StanzaFilter NORMAL_OR_CHAT = new OrFilter(NORMAL, CHAT);
|
||||
public static final StanzaFilter NORMAL_OR_HEADLINE = new OrFilter(NORMAL, HEADLINE);
|
||||
public static final StanzaFilter NORMAL_OR_CHAT_OR_HEADLINE = new OrFilter(NORMAL_OR_CHAT,
|
||||
HEADLINE);
|
||||
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.filter;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Filters for packets with a particular type of stanza extension.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
* @deprecated use {@link StanzaExtensionFilter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PacketExtensionFilter implements StanzaFilter {
|
||||
|
||||
private final String elementName;
|
||||
private final String namespace;
|
||||
|
||||
/**
|
||||
* Creates a new stanza extension filter. Packets will pass the filter if
|
||||
* they have a stanza extension that matches the specified element name
|
||||
* and namespace.
|
||||
*
|
||||
* @param elementName the XML element name of the stanza extension.
|
||||
* @param namespace the XML namespace of the stanza extension.
|
||||
*/
|
||||
public PacketExtensionFilter(String elementName, String namespace) {
|
||||
StringUtils.requireNotNullNorEmpty(namespace, "namespace must not be null nor empty");
|
||||
|
||||
this.elementName = elementName;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stanza extension filter. Packets will pass the filter if they have a packet
|
||||
* extension that matches the specified namespace.
|
||||
*
|
||||
* @param namespace the XML namespace of the stanza extension.
|
||||
*/
|
||||
public PacketExtensionFilter(String namespace) {
|
||||
this(null, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stanza extension filter for the given stanza extension.
|
||||
*
|
||||
* @param packetExtension TODO javadoc me please
|
||||
*/
|
||||
public PacketExtensionFilter(ExtensionElement packetExtension) {
|
||||
this(packetExtension.getElementName(), packetExtension.getNamespace());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Stanza packet) {
|
||||
return packet.hasExtension(elementName, namespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": element=" + elementName + " namespace=" + namespace;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.filter;
|
||||
|
||||
/**
|
||||
* Defines a way to filter packets for particular attributes. Stanza filters are used when
|
||||
* constructing stanza listeners or collectors -- the filter defines what packets match the criteria
|
||||
* of the collector or listener for further stanza processing.
|
||||
* <p>
|
||||
* Several simple filters are pre-defined. These filters can be logically combined for more complex
|
||||
* stanza filtering by using the {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and
|
||||
* {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible to define
|
||||
* your own filters by implementing this interface. The code example below creates a trivial filter
|
||||
* for packets with a specific ID (real code should use {@link StanzaIdFilter} instead).
|
||||
*
|
||||
* <pre>
|
||||
* // Use an anonymous inner class to define a stanza filter that returns
|
||||
* // all packets that have a stanza ID of "RS145".
|
||||
* PacketFilter myFilter = new PacketFilter() {
|
||||
* public boolean accept(Packet packet) {
|
||||
* return "RS145".equals(packet.getStanzaId());
|
||||
* }
|
||||
* };
|
||||
* // Create a new stanza collector using the filter we created.
|
||||
* StanzaCollector myCollector = packetReader.createStanzaCollector(myFilter);
|
||||
* </pre>
|
||||
*
|
||||
* @see org.jivesoftware.smack.StanzaCollector
|
||||
* @see org.jivesoftware.smack.StanzaListener
|
||||
* @author Matt Tucker
|
||||
* @deprecated use {@link StanzaFilter}
|
||||
*/
|
||||
@Deprecated
|
||||
public interface PacketFilter extends StanzaFilter {
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.filter;
|
||||
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Filters for packets with a particular stanza ID.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
* @deprecated use {@link StanzaIdFilter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PacketIDFilter implements StanzaFilter {
|
||||
|
||||
private final String packetID;
|
||||
|
||||
/**
|
||||
* Creates a new stanza ID filter using the specified packet's ID.
|
||||
*
|
||||
* @param packet the stanza which the ID is taken from.
|
||||
* @deprecated use {@link StanzaIdFilter#StanzaIdFilter(Stanza)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketIDFilter(Stanza packet) {
|
||||
this(packet.getStanzaId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stanza ID filter using the specified stanza ID.
|
||||
*
|
||||
* @param packetID the stanza ID to filter for.
|
||||
* @deprecated use {@link StanzaIdFilter#StanzaIdFilter(Stanza)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketIDFilter(String packetID) {
|
||||
StringUtils.requireNotNullNorEmpty(packetID, "Packet ID must not be null nor empty.");
|
||||
this.packetID = packetID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Stanza packet) {
|
||||
return packetID.equals(packet.getStanzaId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": id=" + packetID;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.filter;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
/**
|
||||
* Filters for packets of a particular type. The type is given as a Class object, so
|
||||
* example types would:
|
||||
* <ul>
|
||||
* <li><code>Message.class</code>
|
||||
* <li><code>IQ.class</code>
|
||||
* <li><code>Presence.class</code>
|
||||
* </ul>
|
||||
*
|
||||
* @author Matt Tucker
|
||||
* @deprecated use {@link StanzaTypeFilter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PacketTypeFilter implements StanzaFilter {
|
||||
|
||||
public static final PacketTypeFilter PRESENCE = new PacketTypeFilter(Presence.class);
|
||||
public static final PacketTypeFilter MESSAGE = new PacketTypeFilter(Message.class);
|
||||
|
||||
private final Class<? extends Stanza> packetType;
|
||||
|
||||
/**
|
||||
* Creates a new stanza type filter that will filter for packets that are the
|
||||
* same type as <code>packetType</code>.
|
||||
*
|
||||
* @param packetType the Class type.
|
||||
*/
|
||||
public PacketTypeFilter(Class<? extends Stanza> packetType) {
|
||||
this.packetType = packetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Stanza packet) {
|
||||
return packetType.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": " + packetType.getName();
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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.filter;
|
||||
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* Match based on the 'to' attribute of a Stanza.
|
||||
*
|
||||
* @deprecated use {@link ToMatchesFilter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ToFilter implements StanzaFilter {
|
||||
|
||||
private final Jid to;
|
||||
|
||||
public ToFilter(Jid to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Stanza packet) {
|
||||
Jid packetTo = packet.getTo();
|
||||
if (packetTo == null) {
|
||||
return false;
|
||||
}
|
||||
return packetTo.equals(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": to=" + to;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ import org.jxmpp.jid.Jid;
|
|||
*/
|
||||
public class FromJidTypeFilter extends AbstractJidTypeFilter {
|
||||
|
||||
public static final FromJidTypeFilter ENTITY_BARE_JID = new FromJidTypeFilter(JidType.EntityBareJid);
|
||||
|
||||
public FromJidTypeFilter(JidType jidType) {
|
||||
super(jidType);
|
||||
}
|
||||
|
|
|
@ -445,7 +445,7 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement {
|
|||
* @param extensions a collection of stanza extensions
|
||||
*/
|
||||
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
|
||||
public final void addExtensions(Collection<ExtensionElement> extensions) {
|
||||
public final void addExtensions(Collection<? extends ExtensionElement> extensions) {
|
||||
if (extensions == null) return;
|
||||
for (ExtensionElement packetExtension : extensions) {
|
||||
addExtension(packetExtension);
|
||||
|
|
|
@ -106,5 +106,12 @@ public interface StanzaView extends XmlLangElement {
|
|||
|
||||
List<ExtensionElement> getExtensions(QName qname);
|
||||
|
||||
/**
|
||||
* Return all extension elements of the given type. Returns the empty list if there a none.
|
||||
*
|
||||
* @param <E> the type of extension elements.
|
||||
* @param extensionElementClass the class of the type of extension elements.
|
||||
* @return a list of extension elements of that type, which may be empty.
|
||||
*/
|
||||
<E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 Florian Schmaus
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -30,6 +30,10 @@ public class SmackParsingException extends Exception {
|
|||
super(exception);
|
||||
}
|
||||
|
||||
public SmackParsingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public static class SmackTextParseException extends SmackParsingException {
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -18,9 +18,12 @@ package org.jivesoftware.smack.util;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class CollectionUtil {
|
||||
|
@ -59,6 +62,20 @@ public class CollectionUtil {
|
|||
return new ArrayList<>(collection);
|
||||
}
|
||||
|
||||
public static <T> List<T> cloneAndSeal(Collection<? extends T> collection) {
|
||||
if (collection == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<T> clone = newListWith(collection);
|
||||
return Collections.unmodifiableList(clone);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> cloneAndSeal(Map<K, V> map) {
|
||||
Map<K, V> clone = new HashMap<>(map);
|
||||
return Collections.unmodifiableMap(clone);
|
||||
}
|
||||
|
||||
public static <T> Set<T> newSetWith(Collection<? extends T> collection) {
|
||||
if (collection == null) {
|
||||
return null;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 Florian Schmaus.
|
||||
* Copyright 2019-2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,6 +34,12 @@ public final class EqualsUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
int thisHashCode = thisObject.hashCode();
|
||||
int otherHashCode = other.hashCode();
|
||||
if (thisHashCode != otherHashCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EqualsUtil.Builder equalsBuilder = new EqualsUtil.Builder();
|
||||
|
||||
equalsComperator.compare(equalsBuilder, thisObjectClass.cast(other));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
* Copyright © 2014-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,40 +22,6 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
|||
|
||||
public class PacketUtil {
|
||||
|
||||
/**
|
||||
* Get a extension element from a collection.
|
||||
* @param collection TODO javadoc me please
|
||||
* @param element TODO javadoc me please
|
||||
* @param namespace TODO javadoc me please
|
||||
* @param <PE> the type of the extension element.
|
||||
* @return the extension element
|
||||
* @deprecated use {@link #extensionElementFrom(Collection, String, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <PE extends ExtensionElement> PE packetExtensionfromCollection(
|
||||
Collection<ExtensionElement> collection, String element,
|
||||
String namespace) {
|
||||
return extensionElementFrom(collection, element, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a extension element from a collection.
|
||||
*
|
||||
* @param collection Collection of ExtensionElements.
|
||||
* @param element name of the targeted ExtensionElement.
|
||||
* @param namespace namespace of the targeted ExtensionElement.
|
||||
* @param <PE> Type of the ExtensionElement
|
||||
*
|
||||
* @return the extension element
|
||||
* @deprecated use {@link #extensionElementFrom(Collection, String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static <PE extends ExtensionElement> PE packetExtensionFromCollection(
|
||||
Collection<ExtensionElement> collection, String element,
|
||||
String namespace) {
|
||||
return extensionElementFrom(collection, element, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a extension element from a collection.
|
||||
*
|
||||
|
|
|
@ -20,8 +20,10 @@ package org.jivesoftware.smack.util;
|
|||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -591,4 +593,11 @@ public class StringUtils {
|
|||
}
|
||||
return appendable.append('\n');
|
||||
}
|
||||
|
||||
public static final String PORTABLE_NEWLINE_REGEX = "\\r?\\n";
|
||||
|
||||
public static List<String> splitLinesPortable(String input) {
|
||||
String[] lines = input.split(PORTABLE_NEWLINE_REGEX);
|
||||
return Arrays.asList(lines);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,10 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
|||
.build();
|
||||
}
|
||||
|
||||
public XmlEnvironment getXmlEnvironment() {
|
||||
return effectiveXmlEnvironment;
|
||||
}
|
||||
|
||||
public XmlStringBuilder escapedElement(String name, String escapedContent) {
|
||||
assert escapedContent != null;
|
||||
openElement(name);
|
||||
|
@ -493,6 +497,13 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
|||
return this;
|
||||
}
|
||||
|
||||
public XmlStringBuilder optAppend(Collection<? extends Element> elements) {
|
||||
if (elements != null) {
|
||||
append(elements);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public XmlStringBuilder optTextChild(CharSequence sqc, NamedElement parentElement) {
|
||||
if (sqc == null) {
|
||||
return closeEmptyElement();
|
||||
|
|
|
@ -49,7 +49,7 @@ public class XmppElementUtil {
|
|||
namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null);
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
|
||||
throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMSEPACE or QNAME member. Consider adding QNAME", e);
|
||||
throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e);
|
||||
}
|
||||
|
||||
return new QName(namespace, element);
|
||||
|
|
|
@ -143,6 +143,7 @@ public final class EnhancedDebuggerWindow {
|
|||
debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getComponentCount());
|
||||
tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1);
|
||||
tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon);
|
||||
tabbedPane.setSelectedIndex(tabbedPane.indexOfComponent(debugger.tabbedPane));
|
||||
frame.setTitle(
|
||||
"Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1));
|
||||
// Keep the added debugger for later access
|
||||
|
|
|
@ -278,8 +278,9 @@ public final class MamManager extends Manager {
|
|||
if (dataForm != null) {
|
||||
return dataForm;
|
||||
}
|
||||
dataForm = getNewMamForm();
|
||||
dataForm.addFields(formFields.values());
|
||||
DataForm.Builder dataFormBuilder = getNewMamForm();
|
||||
dataFormBuilder.addFields(formFields.values());
|
||||
dataForm = dataFormBuilder.build();
|
||||
return dataForm;
|
||||
}
|
||||
|
||||
|
@ -330,7 +331,7 @@ public final class MamManager extends Manager {
|
|||
}
|
||||
|
||||
FormField formField = getWithFormField(withJid);
|
||||
formFields.put(formField.getVariable(), formField);
|
||||
formFields.put(formField.getFieldName(), formField);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -341,9 +342,9 @@ public final class MamManager extends Manager {
|
|||
}
|
||||
|
||||
FormField formField = FormField.builder(FORM_FIELD_START)
|
||||
.addValue(start)
|
||||
.setValue(start)
|
||||
.build();
|
||||
formFields.put(formField.getVariable(), formField);
|
||||
formFields.put(formField.getFieldName(), formField);
|
||||
|
||||
FormField endFormField = formFields.get(FORM_FIELD_END);
|
||||
if (endFormField != null) {
|
||||
|
@ -369,9 +370,9 @@ public final class MamManager extends Manager {
|
|||
}
|
||||
|
||||
FormField formField = FormField.builder(FORM_FIELD_END)
|
||||
.addValue(end)
|
||||
.setValue(end)
|
||||
.build();
|
||||
formFields.put(formField.getVariable(), formField);
|
||||
formFields.put(formField.getFieldName(), formField);
|
||||
|
||||
FormField startFormField = formFields.get(FORM_FIELD_START);
|
||||
if (startFormField != null) {
|
||||
|
@ -418,7 +419,7 @@ public final class MamManager extends Manager {
|
|||
}
|
||||
|
||||
public Builder withAdditionalFormField(FormField formField) {
|
||||
formFields.put(formField.getVariable(), formField);
|
||||
formFields.put(formField.getFieldName(), formField);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -483,7 +484,7 @@ public final class MamManager extends Manager {
|
|||
|
||||
private static FormField getWithFormField(Jid withJid) {
|
||||
return FormField.builder(FORM_FIELD_WITH)
|
||||
.addValue(withJid.toString())
|
||||
.setValue(withJid.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -718,9 +719,9 @@ public final class MamManager extends Manager {
|
|||
throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress);
|
||||
}
|
||||
|
||||
private static DataForm getNewMamForm() {
|
||||
FormField field = FormField.hiddenFormType(MamElements.NAMESPACE);
|
||||
DataForm form = new DataForm(DataForm.Type.submit);
|
||||
private static DataForm.Builder getNewMamForm() {
|
||||
FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE);
|
||||
DataForm.Builder form = DataForm.builder();
|
||||
form.addField(field);
|
||||
return form;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2016 Florian Schmaus and Fernando Ramirez
|
||||
* Copyright © 2016-2020 Florian Schmaus and Fernando Ramirez
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,7 +18,6 @@ package org.jivesoftware.smackx.mam.element;
|
|||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
/**
|
||||
|
@ -88,11 +87,11 @@ public class MamQueryIQ extends IQ {
|
|||
this.dataForm = dataForm;
|
||||
|
||||
if (dataForm != null) {
|
||||
FormField field = dataForm.getHiddenFormTypeField();
|
||||
if (field == null) {
|
||||
String formType = dataForm.getFormType();
|
||||
if (formType == null) {
|
||||
throw new IllegalArgumentException("If a data form is given it must posses a hidden form type field");
|
||||
}
|
||||
if (!field.getValues().get(0).toString().equals(MamElements.NAMESPACE)) {
|
||||
if (!formType.equals(MamElements.NAMESPACE)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value of the hidden form type field must be '" + MamElements.NAMESPACE + "'");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.message_fastening;
|
||||
|
||||
import java.util.List;
|
||||
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.MessageBuilder;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.message_fastening.element.FasteningElement;
|
||||
|
||||
/**
|
||||
* Smacks API for XEP-0422: Message Fastening.
|
||||
* The API is still very bare bones, as the XEP intends Message Fastening to be used as a tool by other protocols.
|
||||
*
|
||||
* To enable / disable auto-announcing support for this feature, call {@link #setEnabledByDefault(boolean)} (default true).
|
||||
*
|
||||
* To fasten a payload to a previous message, create an {@link FasteningElement} using the builder provided by
|
||||
* {@link FasteningElement#builder()}.
|
||||
*
|
||||
* You need to provide the {@link org.jivesoftware.smackx.sid.element.OriginIdElement} of the message you want to reference.
|
||||
* Then add wrapped payloads using {@link FasteningElement.Builder#addWrappedPayloads(List)}
|
||||
* and external payloads using {@link FasteningElement.Builder#addExternalPayloads(List)}.
|
||||
*
|
||||
* If you fastened some payloads onto the message previously and now want to replace the previous fastening, call
|
||||
* {@link FasteningElement.Builder#isRemovingElement()}.
|
||||
* Once you are finished, build the {@link FasteningElement} using {@link FasteningElement.Builder#build()} and add it to
|
||||
* a stanza by calling {@link FasteningElement#applyTo(MessageBuilder)}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html">XEP-0422: Message Fastening</a>
|
||||
*/
|
||||
public final class MessageFasteningManager extends Manager {
|
||||
|
||||
public static final String NAMESPACE = "urn:xmpp:fasten:0";
|
||||
|
||||
private static boolean ENABLED_BY_DEFAULT = false;
|
||||
|
||||
private static final WeakHashMap<XMPPConnection, MessageFasteningManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
static {
|
||||
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||
@Override
|
||||
public void connectionCreated(XMPPConnection connection) {
|
||||
if (ENABLED_BY_DEFAULT) {
|
||||
MessageFasteningManager.getInstanceFor(connection).announceSupport();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private MessageFasteningManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
}
|
||||
|
||||
public static synchronized MessageFasteningManager getInstanceFor(XMPPConnection connection) {
|
||||
MessageFasteningManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new MessageFasteningManager(connection);
|
||||
INSTANCES.put(connection, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable auto-announcing support for Message Fastening.
|
||||
* Default is enabled.
|
||||
*
|
||||
* @param enabled enabled
|
||||
*/
|
||||
public static synchronized void setEnabledByDefault(boolean enabled) {
|
||||
ENABLED_BY_DEFAULT = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Announce support for Message Fastening via Service Discovery.
|
||||
*/
|
||||
public void announceSupport() {
|
||||
ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||
discoveryManager.addFeature(NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop announcing support for Message Fastening.
|
||||
*/
|
||||
public void stopAnnouncingSupport() {
|
||||
ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||
discoveryManager.removeFeature(NAMESPACE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.message_fastening.element;
|
||||
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
* Child element of {@link FasteningElement}.
|
||||
* Reference to a top level element in the stanza that contains the {@link FasteningElement}.
|
||||
*/
|
||||
public class ExternalElement implements NamedElement {
|
||||
|
||||
public static final String ELEMENT = "external";
|
||||
public static final String ATTR_NAME = "name";
|
||||
public static final String ATTR_ELEMENT_NAMESPACE = "element-namespace";
|
||||
|
||||
private final String name;
|
||||
private final String elementNamespace;
|
||||
|
||||
/**
|
||||
* Create a new {@link ExternalElement} that references a top level element with the given name.
|
||||
*
|
||||
* @param name name of the top level element
|
||||
*/
|
||||
public ExternalElement(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExternalElement} that references a top level element with the given name and namespace.
|
||||
*
|
||||
* @param name name of the top level element
|
||||
* @param elementNamespace namespace of the top level element
|
||||
*/
|
||||
public ExternalElement(String name, String elementNamespace) {
|
||||
this.name = name;
|
||||
this.elementNamespace = elementNamespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute(ATTR_NAME, getName());
|
||||
xml.optAttribute(ATTR_ELEMENT_NAMESPACE, getElementNamespace());
|
||||
return xml.closeEmptyElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the referenced top level element, eg. 'body'.
|
||||
* @return element name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Namespace of the referenced top level element, eg. 'urn:example:lik'.
|
||||
* @return element namespace
|
||||
*/
|
||||
public String getElementNamespace() {
|
||||
return elementNamespace;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.message_fastening.element;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smackx.message_fastening.MessageFasteningManager;
|
||||
import org.jivesoftware.smackx.sid.element.OriginIdElement;
|
||||
|
||||
/**
|
||||
* Message Fastening container element.
|
||||
*/
|
||||
public final class FasteningElement implements ExtensionElement {
|
||||
|
||||
public static final String ELEMENT = "apply-to";
|
||||
public static final String NAMESPACE = MessageFasteningManager.NAMESPACE;
|
||||
public static final String ATTR_ID = "id";
|
||||
public static final String ATTR_CLEAR = "clear";
|
||||
public static final String ATTR_SHELL = "shell";
|
||||
|
||||
private final OriginIdElement referencedStanzasOriginId;
|
||||
private final List<ExternalElement> externalPayloads = new ArrayList<>();
|
||||
private final List<ExtensionElement> wrappedPayloads = new ArrayList<>();
|
||||
private final boolean clear;
|
||||
private final boolean shell;
|
||||
|
||||
private FasteningElement(OriginIdElement originId,
|
||||
List<ExtensionElement> wrappedPayloads,
|
||||
List<ExternalElement> externalPayloads,
|
||||
boolean clear,
|
||||
boolean shell) {
|
||||
this.referencedStanzasOriginId = Objects.requireNonNull(originId, "Fastening element MUST contain an origin-id.");
|
||||
this.wrappedPayloads.addAll(wrappedPayloads);
|
||||
this.externalPayloads.addAll(externalPayloads);
|
||||
this.clear = clear;
|
||||
this.shell = shell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OriginIdElement origin-id} of the {@link Stanza} that the message fastenings are to be
|
||||
* applied to.
|
||||
*
|
||||
* @return origin id of the referenced stanza
|
||||
*/
|
||||
public OriginIdElement getReferencedStanzasOriginId() {
|
||||
return referencedStanzasOriginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all wrapped payloads of this element.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#wrapped-payloads">XEP-0422: §3.1. Wrapped Payloads</a>
|
||||
*
|
||||
* @return wrapped payloads.
|
||||
*/
|
||||
public List<ExtensionElement> getWrappedPayloads() {
|
||||
return Collections.unmodifiableList(wrappedPayloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all external payloads of this element.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#external-payloads">XEP-0422: §3.2. External Payloads</a>
|
||||
*
|
||||
* @return external payloads.
|
||||
*/
|
||||
public List<ExternalElement> getExternalPayloads() {
|
||||
return Collections.unmodifiableList(externalPayloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this element remove a previously sent {@link FasteningElement}?
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#remove">
|
||||
* XEP-0422: Message Fastening §3.4 Removing fastenings</a>
|
||||
*
|
||||
* @return true if the clear attribute is set.
|
||||
*/
|
||||
public boolean isRemovingElement() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a shell element?
|
||||
* Shell elements are otherwise empty elements that indicate that an encrypted payload of a message
|
||||
* encrypted using XEP-420: Stanza Content Encryption contains a sensitive {@link FasteningElement}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#encryption">
|
||||
* XEP-0422: Message Fastening §3.5 Interaction with stanza encryption</a>
|
||||
*
|
||||
* @return true if this is a shell element.
|
||||
*/
|
||||
public boolean isShellElement() {
|
||||
return shell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the provided {@link Message} contains a {@link FasteningElement}.
|
||||
*
|
||||
* @param message message
|
||||
* @return true if the stanza has an {@link FasteningElement}.
|
||||
*/
|
||||
public static boolean hasFasteningElement(Message message) {
|
||||
return message.hasExtension(ELEMENT, MessageFasteningManager.NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the provided {@link MessageBuilder} contains a {@link FasteningElement}.
|
||||
*
|
||||
* @param builder message builder
|
||||
* @return true if the stanza has an {@link FasteningElement}.
|
||||
*/
|
||||
public static boolean hasFasteningElement(MessageBuilder builder) {
|
||||
return builder.hasExtension(FasteningElement.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return MessageFasteningManager.NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this)
|
||||
.attribute(ATTR_ID, referencedStanzasOriginId.getId())
|
||||
.optBooleanAttribute(ATTR_CLEAR, isRemovingElement())
|
||||
.optBooleanAttribute(ATTR_SHELL, isShellElement())
|
||||
.rightAngleBracket();
|
||||
addPayloads(xml);
|
||||
return xml.closeElement(this);
|
||||
}
|
||||
|
||||
private void addPayloads(XmlStringBuilder xml) {
|
||||
for (ExternalElement external : externalPayloads) {
|
||||
xml.append(external);
|
||||
}
|
||||
for (ExtensionElement wrapped : wrappedPayloads) {
|
||||
xml.append(wrapped);
|
||||
}
|
||||
}
|
||||
|
||||
public static FasteningElement createShellElementForSensitiveElement(FasteningElement sensitiveElement) {
|
||||
return createShellElementForSensitiveElement(sensitiveElement.getReferencedStanzasOriginId());
|
||||
}
|
||||
|
||||
public static FasteningElement createShellElementForSensitiveElement(String originIdOfSensitiveElement) {
|
||||
return createShellElementForSensitiveElement(new OriginIdElement(originIdOfSensitiveElement));
|
||||
}
|
||||
|
||||
public static FasteningElement createShellElementForSensitiveElement(OriginIdElement originIdOfSensitiveElement) {
|
||||
return FasteningElement.builder()
|
||||
.setOriginId(originIdOfSensitiveElement)
|
||||
.setShell()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this element to the provided message builder.
|
||||
* Note: The stanza MUST NOT contain more than one apply-to elements at the same time.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#rules">XEP-0422 §4: Business Rules</a>
|
||||
*
|
||||
* @param messageBuilder message builder
|
||||
*/
|
||||
public void applyTo(MessageBuilder messageBuilder) {
|
||||
if (FasteningElement.hasFasteningElement(messageBuilder)) {
|
||||
throw new IllegalArgumentException("Stanza cannot contain more than one apply-to elements.");
|
||||
} else {
|
||||
messageBuilder.addExtension(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private OriginIdElement originId;
|
||||
private final List<ExtensionElement> wrappedPayloads = new ArrayList<>();
|
||||
private final List<ExternalElement> externalPayloads = new ArrayList<>();
|
||||
private boolean isClear = false;
|
||||
private boolean isShell = false;
|
||||
|
||||
/**
|
||||
* Set the origin-id of the referenced message.
|
||||
*
|
||||
* @param originIdString origin id as String
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder setOriginId(String originIdString) {
|
||||
return setOriginId(new OriginIdElement(originIdString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link OriginIdElement} of the referenced message.
|
||||
*
|
||||
* @param originId origin-id as element
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder setOriginId(OriginIdElement originId) {
|
||||
this.originId = originId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a wrapped payload.
|
||||
*
|
||||
* @param wrappedPayload wrapped payload
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder addWrappedPayload(ExtensionElement wrappedPayload) {
|
||||
return addWrappedPayloads(Collections.singletonList(wrappedPayload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple wrapped payloads at once.
|
||||
*
|
||||
* @param wrappedPayloads list of wrapped payloads
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder addWrappedPayloads(List<ExtensionElement> wrappedPayloads) {
|
||||
this.wrappedPayloads.addAll(wrappedPayloads);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an external payload.
|
||||
*
|
||||
* @param externalPayload external payload
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder addExternalPayload(ExternalElement externalPayload) {
|
||||
return addExternalPayloads(Collections.singletonList(externalPayload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple external payloads at once.
|
||||
*
|
||||
* @param externalPayloads external payloads
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder addExternalPayloads(List<ExternalElement> externalPayloads) {
|
||||
this.externalPayloads.addAll(externalPayloads);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare this {@link FasteningElement} to remove previous fastenings.
|
||||
* Semantically the wrapped payloads of this element declares all wrapped payloads from the referenced
|
||||
* fastening element that share qualified names as removed.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#remove">
|
||||
* XEP-0422: Message Fastening §3.4 Removing fastenings</a>
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder setClear() {
|
||||
isClear = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare this {@link FasteningElement} to be a shell element.
|
||||
* Shell elements are used as hints that a Stanza Content Encryption payload contains another sensitive
|
||||
* {@link FasteningElement}. The outer "shell" {@link FasteningElement} is used to do fastening collation.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#encryption">XEP-0422: Message Fastening §3.5 Interaction with stanza encryption</a>
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0420.html">XEP-0420: Stanza Content Encryption</a>
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
public Builder setShell() {
|
||||
isShell = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the element.
|
||||
* @return built element.
|
||||
*/
|
||||
public FasteningElement build() {
|
||||
validateThatIfIsShellThenOtherwiseEmpty();
|
||||
return new FasteningElement(originId, wrappedPayloads, externalPayloads, isClear, isShell);
|
||||
}
|
||||
|
||||
private void validateThatIfIsShellThenOtherwiseEmpty() {
|
||||
if (!isShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isClear || !wrappedPayloads.isEmpty() || !externalPayloads.isEmpty()) {
|
||||
throw new IllegalArgumentException("A fastening that is a shell element must be otherwise empty " +
|
||||
"and cannot have a 'clear' attribute.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* XEP-0422: Message Fastening.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html">XEP-0422: Message
|
||||
* Fastening</a>
|
||||
*
|
||||
*/
|
||||
package org.jivesoftware.smackx.message_fastening.element;
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* XEP-0422: Message Fastening.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html">XEP-0422: Message
|
||||
* Fastening</a>
|
||||
*
|
||||
*/
|
||||
package org.jivesoftware.smackx.message_fastening;
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.message_fastening.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smackx.message_fastening.MessageFasteningManager;
|
||||
import org.jivesoftware.smackx.message_fastening.element.ExternalElement;
|
||||
import org.jivesoftware.smackx.message_fastening.element.FasteningElement;
|
||||
|
||||
public class FasteningElementProvider extends ExtensionElementProvider<FasteningElement> {
|
||||
|
||||
public static final FasteningElementProvider TEST_INSTANCE = new FasteningElementProvider();
|
||||
|
||||
@Override
|
||||
public FasteningElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
|
||||
FasteningElement.Builder builder = FasteningElement.builder();
|
||||
builder.setOriginId(parser.getAttributeValue("", FasteningElement.ATTR_ID));
|
||||
if (ParserUtils.getBooleanAttribute(parser, FasteningElement.ATTR_CLEAR, false)) {
|
||||
builder.setClear();
|
||||
}
|
||||
if (ParserUtils.getBooleanAttribute(parser, FasteningElement.ATTR_SHELL, false)) {
|
||||
builder.setShell();
|
||||
}
|
||||
|
||||
outerloop: while (true) {
|
||||
XmlPullParser.Event tag = parser.next();
|
||||
switch (tag) {
|
||||
case START_ELEMENT:
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
|
||||
// Parse external payload
|
||||
if (MessageFasteningManager.NAMESPACE.equals(namespace) && ExternalElement.ELEMENT.equals(name)) {
|
||||
ExternalElement external = new ExternalElement(
|
||||
parser.getAttributeValue("", ExternalElement.ATTR_NAME),
|
||||
parser.getAttributeValue("", ExternalElement.ATTR_ELEMENT_NAMESPACE));
|
||||
builder.addExternalPayload(external);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse wrapped payload
|
||||
ExtensionElement wrappedPayload = PacketParserUtils.parseExtensionElement(name, namespace, parser, xmlEnvironment);
|
||||
builder.addWrappedPayload(wrappedPayload);
|
||||
break;
|
||||
|
||||
case END_ELEMENT:
|
||||
if (parser.getDepth() == initialDepth) {
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* XEP-0422: Message Fastening.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html">XEP-0422: Message
|
||||
* Fastening</a>
|
||||
*
|
||||
*/
|
||||
package org.jivesoftware.smackx.message_fastening.provider;
|
|
@ -24,6 +24,7 @@ import org.jivesoftware.smack.packet.IQ;
|
|||
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
@ -98,24 +99,23 @@ public class EnablePushNotificationsIQ extends IQ {
|
|||
xml.rightAngleBracket();
|
||||
|
||||
if (publishOptions != null) {
|
||||
DataForm dataForm = new DataForm(DataForm.Type.submit);
|
||||
DataForm.Builder dataForm = DataForm.builder();
|
||||
|
||||
// TODO: Shouldn't this use some potentially existing PubSub API? Also FORM_TYPE fields are usually of type
|
||||
// 'hidden', but the examples in XEP-0357 do also not set the value to hidden and FORM_TYPE itself appears
|
||||
// to be more convention than specification.
|
||||
FormField.Builder formTypeField = FormField.builder("FORM_TYPE");
|
||||
formTypeField.addValue(PubSub.NAMESPACE + "#publish-options");
|
||||
dataForm.addField(formTypeField.build());
|
||||
FormField formTypeField = FormField.buildHiddenFormType(PubSub.NAMESPACE + "#publish-options");
|
||||
dataForm.addField(formTypeField);
|
||||
|
||||
Iterator<Map.Entry<String, String>> publishOptionsIterator = publishOptions.entrySet().iterator();
|
||||
while (publishOptionsIterator.hasNext()) {
|
||||
Map.Entry<String, String> pairVariableValue = publishOptionsIterator.next();
|
||||
FormField.Builder field = FormField.builder(pairVariableValue.getKey());
|
||||
field.addValue(pairVariableValue.getValue());
|
||||
TextSingleFormField.Builder field = FormField.builder(pairVariableValue.getKey());
|
||||
field.setValue(pairVariableValue.getValue());
|
||||
dataForm.addField(field.build());
|
||||
}
|
||||
|
||||
xml.append(dataForm);
|
||||
xml.append(dataForm.build());
|
||||
}
|
||||
|
||||
return xml;
|
||||
|
|
|
@ -101,4 +101,25 @@ public class OriginIdElement extends StableAndUniqueIdElement {
|
|||
.attribute(ATTR_ID, getId())
|
||||
.closeEmptyElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof OriginIdElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OriginIdElement otherId = (OriginIdElement) other;
|
||||
return getId().equals(otherId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -292,6 +292,12 @@
|
|||
<className>org.jivesoftware.smackx.dox.provider.DnsIqProvider</className>
|
||||
</iqProvider>
|
||||
|
||||
<!-- XEP-0422: Message Fastening -->
|
||||
<extensionProvider>
|
||||
<elementName>apply-to</elementName>
|
||||
<namespace>urn:xmpp:fasten:0</namespace>
|
||||
<className>org.jivesoftware.smackx.message_fastening.provider.FasteningElementProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
<!-- XEP-xxxx: Multi-User Chat Light -->
|
||||
<iqProvider>
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
<className>org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager</className>
|
||||
<className>org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager</className>
|
||||
<className>org.jivesoftware.smackx.xmlelement.DataFormsXmlElementManager</className>
|
||||
<className>org.jivesoftware.smackx.message_fastening.MessageFasteningManager</className>
|
||||
</startupClasses>
|
||||
</smack>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus
|
||||
* Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,7 +34,7 @@ import org.jxmpp.util.XmppDateTime;
|
|||
public class FiltersTest extends MamTest {
|
||||
|
||||
private static String getMamXMemberWith(List<String> fieldsNames, List<? extends CharSequence> fieldsValues) {
|
||||
String xml = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>" + "<value>"
|
||||
String xml = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>";
|
||||
|
||||
for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) {
|
||||
|
|
|
@ -85,7 +85,7 @@ public class MamQueryIQProviderTest {
|
|||
assertEquals(fields2.get(1).getType(), FormField.Type.jid_single);
|
||||
assertEquals(fields2.get(2).getType(), FormField.Type.text_single);
|
||||
assertEquals(fields2.get(2).getValues(), new ArrayList<>());
|
||||
assertEquals(fields2.get(4).getVariable(), "urn:example:xmpp:free-text-search");
|
||||
assertEquals(fields2.get(4).getFieldName(), "urn:example:xmpp:free-text-search");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ public class MamTest extends SmackTestSuite {
|
|||
IllegalArgumentException, InvocationTargetException {
|
||||
Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm");
|
||||
methodGetNewMamForm.setAccessible(true);
|
||||
return (DataForm) methodGetNewMamForm.invoke(mamManager);
|
||||
DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager);
|
||||
return dataFormBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test;
|
|||
public class PagingTest extends MamTest {
|
||||
|
||||
private static final String pagingStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>"
|
||||
+ "<value>urn:xmpp:mam:1</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.jxmpp.jid.impl.JidCreate;
|
|||
public class QueryArchiveTest extends MamTest {
|
||||
|
||||
private static final String mamSimpleQueryIQ = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>" + "<value>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
|
||||
|
||||
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2016 Fernando Ramirez, 2018-2019 Florian Schmaus
|
||||
* Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test;
|
|||
public class ResultsLimitTest extends MamTest {
|
||||
|
||||
private static final String resultsLimitStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>" + "<value>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2016 Fernando Ramirez, 2018-2019 Florian Schmaus
|
||||
* Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -28,16 +28,17 @@ import org.jivesoftware.smackx.xdata.FormField;
|
|||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.jxmpp.jid.JidTestUtil;
|
||||
|
||||
public class RetrieveFormFieldsTest extends MamTest {
|
||||
|
||||
private static final String retrieveFormFieldStanza = "<iq id='sarasa' type='get'>" + "<query xmlns='" + MamElements.NAMESPACE
|
||||
+ "' queryid='testid'></query>" + "</iq>";
|
||||
|
||||
private static final String additionalFieldsStanza = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE' type='hidden'>"
|
||||
private static final String additionalFieldsStanza = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>"
|
||||
+ "<value>" + MamElements.NAMESPACE + "</value>" + "</field>"
|
||||
+ "<field var='urn:example:xmpp:free-text-search'>" + "<value>Hi</value>" + "</field>"
|
||||
+ "<field var='urn:example:xmpp:stanza-content' type='jid-single'>" + "<value>Hi2</value>" + "</field>"
|
||||
+ "<field var='urn:example:xmpp:stanza-content'>" + "<value>one@exampleone.org</value>" + "</field>"
|
||||
+ "</x>";
|
||||
|
||||
@Test
|
||||
|
@ -51,13 +52,11 @@ public class RetrieveFormFieldsTest extends MamTest {
|
|||
@Test
|
||||
public void checkAddAdditionalFieldsStanza() throws Exception {
|
||||
FormField field1 = FormField.builder("urn:example:xmpp:free-text-search")
|
||||
.setType(FormField.Type.text_single)
|
||||
.addValue("Hi")
|
||||
.setValue("Hi")
|
||||
.build();
|
||||
|
||||
FormField field2 = FormField.builder("urn:example:xmpp:stanza-content")
|
||||
.setType(FormField.Type.jid_single)
|
||||
.addValue("Hi2")
|
||||
FormField field2 = FormField.jidSingleBuilder("urn:example:xmpp:stanza-content")
|
||||
.setValue(JidTestUtil.BARE_JID_1)
|
||||
.build();
|
||||
|
||||
MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 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.message_fastening;
|
||||
|
||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.StandardExtensionElement;
|
||||
import org.jivesoftware.smack.packet.StanzaFactory;
|
||||
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.test.util.SmackTestUtil;
|
||||
import org.jivesoftware.smack.test.util.TestUtils;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smackx.message_fastening.element.ExternalElement;
|
||||
import org.jivesoftware.smackx.message_fastening.element.FasteningElement;
|
||||
import org.jivesoftware.smackx.message_fastening.provider.FasteningElementProvider;
|
||||
import org.jivesoftware.smackx.sid.element.OriginIdElement;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
public class MessageFasteningElementsTest {
|
||||
|
||||
private final StanzaFactory stanzaFactory = new StanzaFactory(new StandardStanzaIdSource());
|
||||
|
||||
/**
|
||||
* Test XML serialization of the {@link FasteningElement} using the example provided by
|
||||
* the XEP.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#wrapped-payloads">XEP-0422 §3.1 Wrapped Payloads</a>
|
||||
*/
|
||||
@Test
|
||||
public void fasteningElementSerializationTest() {
|
||||
String xml =
|
||||
"<apply-to xmlns='urn:xmpp:fasten:0' id='origin-id-1'>" +
|
||||
" <i-like-this xmlns='urn:example:like'/>" +
|
||||
"</apply-to>";
|
||||
|
||||
FasteningElement applyTo = FasteningElement.builder()
|
||||
.setOriginId("origin-id-1")
|
||||
.addWrappedPayload(new StandardExtensionElement("i-like-this", "urn:example:like"))
|
||||
.build();
|
||||
|
||||
assertXmlSimilar(xml, applyTo.toXML().toString());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
|
||||
public void fasteningDeserializationTest(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException {
|
||||
String xml =
|
||||
"<apply-to xmlns='urn:xmpp:fasten:0' id='origin-id-1'>" +
|
||||
" <i-like-this xmlns='urn:example:like'/>" +
|
||||
" <external name='custom' element-namespace='urn:example:custom'/>" +
|
||||
" <external name='body'/>" +
|
||||
"</apply-to>";
|
||||
|
||||
FasteningElement parsed = SmackTestUtil.parse(xml, FasteningElementProvider.class, parserKind);
|
||||
|
||||
assertNotNull(parsed);
|
||||
assertEquals(new OriginIdElement("origin-id-1"), parsed.getReferencedStanzasOriginId());
|
||||
assertFalse(parsed.isRemovingElement());
|
||||
assertFalse(parsed.isShellElement());
|
||||
|
||||
assertEquals(1, parsed.getWrappedPayloads().size());
|
||||
assertEquals("i-like-this", parsed.getWrappedPayloads().get(0).getElementName());
|
||||
assertEquals("urn:example:like", parsed.getWrappedPayloads().get(0).getNamespace());
|
||||
|
||||
assertEquals(2, parsed.getExternalPayloads().size());
|
||||
ExternalElement custom = parsed.getExternalPayloads().get(0);
|
||||
assertEquals("custom", custom.getName());
|
||||
assertEquals("urn:example:custom", custom.getElementNamespace());
|
||||
ExternalElement body = parsed.getExternalPayloads().get(1);
|
||||
assertEquals("body", body.getName());
|
||||
assertNull(body.getElementNamespace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fasteningDeserializationClearTest() throws XmlPullParserException, IOException, SmackParsingException {
|
||||
String xml =
|
||||
"<apply-to xmlns='urn:xmpp:fasten:0' id='origin-id-1' clear='true'>" +
|
||||
" <i-like-this xmlns='urn:example:like'/>" +
|
||||
"</apply-to>";
|
||||
|
||||
FasteningElement parsed = FasteningElementProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml));
|
||||
|
||||
assertTrue(parsed.isRemovingElement());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fasteningElementWithExternalElementsTest() {
|
||||
String xml =
|
||||
"<apply-to xmlns='urn:xmpp:fasten:0' id='origin-id-2'>" +
|
||||
" <external name='body'/>" +
|
||||
" <external name='custom' element-namespace='urn:example:custom'/>" +
|
||||
" <edit xmlns='urn:example.edit'/>" +
|
||||
"</apply-to>";
|
||||
|
||||
FasteningElement element = FasteningElement.builder()
|
||||
.setOriginId("origin-id-2")
|
||||
.addExternalPayloads(Arrays.asList(
|
||||
new ExternalElement("body"),
|
||||
new ExternalElement("custom", "urn:example:custom")
|
||||
))
|
||||
.addWrappedPayload(
|
||||
new StandardExtensionElement("edit", "urn:example.edit"))
|
||||
.build();
|
||||
|
||||
assertXmlSimilar(xml, element.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createShellElementSharesOriginIdTest() {
|
||||
OriginIdElement originIdElement = new OriginIdElement("sensitive-stanza-1");
|
||||
FasteningElement sensitiveFastening = FasteningElement.builder()
|
||||
.setOriginId(originIdElement)
|
||||
.build();
|
||||
|
||||
FasteningElement shellElement = FasteningElement.createShellElementForSensitiveElement(sensitiveFastening);
|
||||
|
||||
assertEquals(originIdElement, shellElement.getReferencedStanzasOriginId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fasteningRemoveSerializationTest() {
|
||||
String xml =
|
||||
"<apply-to xmlns='urn:xmpp:fasten:0' id='origin-id-1' clear='true'>" +
|
||||
" <i-like-this xmlns='urn:example:like'>Very much</i-like-this>" +
|
||||
"</apply-to>";
|
||||
|
||||
FasteningElement element = FasteningElement.builder()
|
||||
.setOriginId("origin-id-1")
|
||||
.setClear()
|
||||
.addWrappedPayload(StandardExtensionElement.builder("i-like-this", "urn:example:like")
|
||||
.setText("Very much")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
assertXmlSimilar(xml, element.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasFasteningElementTest() {
|
||||
MessageBuilder messageBuilderWithFasteningElement = MessageBuilder.buildMessage()
|
||||
.setBody("Hi!")
|
||||
.addExtension(FasteningElement.builder().setOriginId("origin-id-1").build());
|
||||
MessageBuilder messageBuilderWithoutFasteningElement = MessageBuilder.buildMessage()
|
||||
.setBody("Ho!");
|
||||
|
||||
assertTrue(FasteningElement.hasFasteningElement(messageBuilderWithFasteningElement));
|
||||
assertFalse(FasteningElement.hasFasteningElement(messageBuilderWithoutFasteningElement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shellElementMustNotHaveClearAttributeTest() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
FasteningElement.builder()
|
||||
.setShell()
|
||||
.setClear()
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shellElementMustNotContainAnyPayloads() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
FasteningElement.builder()
|
||||
.setShell()
|
||||
.addWrappedPayload(new StandardExtensionElement("edit", "urn:example.edit"))
|
||||
.build());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
FasteningElement.builder()
|
||||
.setShell()
|
||||
.addExternalPayload(new ExternalElement("body"))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureAddFasteningElementToStanzaWorks() {
|
||||
MessageBuilder message = stanzaFactory.buildMessageStanza();
|
||||
FasteningElement fasteningElement = FasteningElement.builder().setOriginId("another-apply-to").build();
|
||||
|
||||
// Adding only one element is allowed
|
||||
fasteningElement.applyTo(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure, that {@link FasteningElement#applyTo(MessageBuilder)}
|
||||
* throws when trying to add an {@link FasteningElement} to a {@link MessageBuilder} that already contains one
|
||||
* such element.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0422.html#rules">XEP-0422: §4. Business Rules</a>
|
||||
*/
|
||||
@Test
|
||||
public void ensureStanzaCanOnlyContainOneFasteningElement() {
|
||||
MessageBuilder messageWithFastening = stanzaFactory.buildMessageStanza();
|
||||
FasteningElement.builder().setOriginId("origin-id").build().applyTo(messageWithFastening);
|
||||
|
||||
// Adding a second fastening MUST result in exception
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () ->
|
||||
FasteningElement.builder().setOriginId("another-apply-to").build()
|
||||
.applyTo(messageWithFastening));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2016-2019 Florian Schmaus
|
||||
* Copyright 2016-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -29,7 +29,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
|||
|
||||
import org.jivesoftware.smackx.commands.AdHocCommandManager;
|
||||
import org.jivesoftware.smackx.commands.RemoteCommand;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
@ -72,7 +72,7 @@ public class ServiceAdministrationManager extends Manager {
|
|||
RemoteCommand command = addUser();
|
||||
command.execute();
|
||||
|
||||
Form answerForm = command.getForm().createAnswerForm();
|
||||
FillableForm answerForm = new FillableForm(command.getForm());
|
||||
|
||||
answerForm.setAnswer("accountjid", userJid);
|
||||
answerForm.setAnswer("password", password);
|
||||
|
@ -101,7 +101,7 @@ public class ServiceAdministrationManager extends Manager {
|
|||
RemoteCommand command = deleteUser();
|
||||
command.execute();
|
||||
|
||||
Form answerForm = command.getForm().createAnswerForm();
|
||||
FillableForm answerForm = new FillableForm(command.getForm());
|
||||
|
||||
answerForm.setAnswer("accountjids", jidsToDelete);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2009 Jonas Ådahl, 2011-2019 Florian Schmaus
|
||||
* Copyright © 2009 Jonas Ådahl, 2011-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -19,13 +19,16 @@ package org.jivesoftware.smackx.caps;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.WeakHashMap;
|
||||
|
@ -48,7 +51,6 @@ import org.jivesoftware.smack.filter.PresenceTypeFilter;
|
|||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaTypeFilter;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.PresenceBuilder;
|
||||
|
@ -548,7 +550,7 @@ public final class EntityCapsManager extends Manager {
|
|||
final List<Identity> identities = new LinkedList<>(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities());
|
||||
sdm.setNodeInformationProvider(localNodeVer, new AbstractNodeInformationProvider() {
|
||||
List<String> features = sdm.getFeatures();
|
||||
List<ExtensionElement> packetExtensions = sdm.getExtendedInfoAsList();
|
||||
List<DataForm> packetExtensions = sdm.getExtendedInfo();
|
||||
@Override
|
||||
public List<String> getNodeFeatures() {
|
||||
return features;
|
||||
|
@ -558,7 +560,7 @@ public final class EntityCapsManager extends Manager {
|
|||
return identities;
|
||||
}
|
||||
@Override
|
||||
public List<ExtensionElement> getNodePacketExtensions() {
|
||||
public List<DataForm> getNodePacketExtensions() {
|
||||
return packetExtensions;
|
||||
}
|
||||
});
|
||||
|
@ -600,7 +602,7 @@ public final class EntityCapsManager extends Manager {
|
|||
return false;
|
||||
|
||||
// step 3.5 check for well-formed packet extensions
|
||||
if (verifyPacketExtensions(info))
|
||||
if (!verifyPacketExtensions(info))
|
||||
return false;
|
||||
|
||||
String calculatedVer = generateVerificationString(info, hash).version;
|
||||
|
@ -612,27 +614,29 @@ public final class EntityCapsManager extends Manager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Verify that the given discovery info is not ill-formed.
|
||||
*
|
||||
* @param info TODO javadoc me please
|
||||
* @return true if the stanza extensions is ill-formed
|
||||
* @param info the discovery info to verify.
|
||||
* @return true if the stanza extensions is not ill-formed
|
||||
*/
|
||||
protected static boolean verifyPacketExtensions(DiscoverInfo info) {
|
||||
List<FormField> foundFormTypes = new LinkedList<>();
|
||||
for (ExtensionElement pe : info.getExtensions()) {
|
||||
if (pe.getNamespace().equals(DataForm.NAMESPACE)) {
|
||||
DataForm df = (DataForm) pe;
|
||||
for (FormField f : df.getFields()) {
|
||||
if (f.getVariable().equals("FORM_TYPE")) {
|
||||
for (FormField fft : foundFormTypes) {
|
||||
if (f.equals(fft))
|
||||
return true;
|
||||
}
|
||||
foundFormTypes.add(f);
|
||||
}
|
||||
}
|
||||
private static boolean verifyPacketExtensions(DiscoverInfo info) {
|
||||
Set<String> foundFormTypes = new HashSet<>();
|
||||
List<DataForm> dataForms = info.getExtensions(DataForm.class);
|
||||
for (DataForm dataForm : dataForms) {
|
||||
FormField formFieldTypeField = dataForm.getHiddenFormTypeField();
|
||||
if (formFieldTypeField == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String type = formFieldTypeField.getFirstValue();
|
||||
boolean noDuplicate = foundFormTypes.add(type);
|
||||
if (!noDuplicate) {
|
||||
// Ill-formed extension: duplicate forms (by form field type string).
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static CapsVersionAndHash generateVerificationString(DiscoverInfoView discoverInfo) {
|
||||
|
@ -664,8 +668,6 @@ public final class EntityCapsManager extends Manager {
|
|||
// be "broken" implementation in the wild, so we *always* transform to lowercase.
|
||||
hash = hash.toLowerCase(Locale.US);
|
||||
|
||||
DataForm extendedInfo = DataForm.from(discoverInfo);
|
||||
|
||||
// 1. Initialize an empty string S ('sb' in this method).
|
||||
StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't
|
||||
// need thread-safe StringBuffer
|
||||
|
@ -705,50 +707,47 @@ public final class EntityCapsManager extends Manager {
|
|||
sb.append('<');
|
||||
}
|
||||
|
||||
// only use the data form for calculation is it has a hidden FORM_TYPE
|
||||
// field
|
||||
// see XEP-0115 5.4 step 3.6
|
||||
if (extendedInfo != null && extendedInfo.hasHiddenFormTypeField()) {
|
||||
synchronized (extendedInfo) {
|
||||
// 6. If the service discovery information response includes
|
||||
// XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
|
||||
// by the XML character data of the <value/> element).
|
||||
SortedSet<FormField> fs = new TreeSet<>(new Comparator<FormField>() {
|
||||
@Override
|
||||
public int compare(FormField f1, FormField f2) {
|
||||
return f1.getVariable().compareTo(f2.getVariable());
|
||||
}
|
||||
});
|
||||
List<DataForm> extendedInfos = discoverInfo.getExtensions(DataForm.class);
|
||||
for (DataForm extendedInfo : extendedInfos) {
|
||||
if (!extendedInfo.hasHiddenFormTypeField()) {
|
||||
// Only use the data form for calculation is it has a hidden FORM_TYPE field.
|
||||
// See XEP-0115 5.4 step 3.f
|
||||
continue;
|
||||
}
|
||||
|
||||
FormField ft = null;
|
||||
|
||||
for (FormField f : extendedInfo.getFields()) {
|
||||
if (!f.getVariable().equals("FORM_TYPE")) {
|
||||
fs.add(f);
|
||||
} else {
|
||||
ft = f;
|
||||
}
|
||||
// 6. If the service discovery information response includes
|
||||
// XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
|
||||
// by the XML character data of the <value/> element).
|
||||
SortedSet<FormField> fs = new TreeSet<>(new Comparator<FormField>() {
|
||||
@Override
|
||||
public int compare(FormField f1, FormField f2) {
|
||||
return f1.getFieldName().compareTo(f2.getFieldName());
|
||||
}
|
||||
});
|
||||
|
||||
// Add FORM_TYPE values
|
||||
if (ft != null) {
|
||||
formFieldValuesToCaps(ft.getValues(), sb);
|
||||
}
|
||||
|
||||
// 7. 3. For each field other than FORM_TYPE:
|
||||
// 1. Append the value of the "var" attribute, followed by the
|
||||
// '<' character.
|
||||
// 2. Sort values by the XML character data of the <value/>
|
||||
// element.
|
||||
// 3. For each <value/> element, append the XML character data,
|
||||
// followed by the '<' character.
|
||||
for (FormField f : fs) {
|
||||
sb.append(f.getVariable());
|
||||
sb.append('<');
|
||||
formFieldValuesToCaps(f.getValues(), sb);
|
||||
for (FormField f : extendedInfo.getFields()) {
|
||||
if (!f.getFieldName().equals("FORM_TYPE")) {
|
||||
fs.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Add FORM_TYPE values
|
||||
formFieldValuesToCaps(Collections.singletonList(extendedInfo.getFormType()), sb);
|
||||
|
||||
// 7. 3. For each field other than FORM_TYPE:
|
||||
// 1. Append the value of the "var" attribute, followed by the
|
||||
// '<' character.
|
||||
// 2. Sort values by the XML character data of the <value/>
|
||||
// element.
|
||||
// 3. For each <value/> element, append the XML character data,
|
||||
// followed by the '<' character.
|
||||
for (FormField f : fs) {
|
||||
sb.append(f.getFieldName());
|
||||
sb.append('<');
|
||||
formFieldValuesToCaps(f.getValues(), sb);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Ensure that S is encoded according to the UTF-8 encoding (RFC
|
||||
// 3269).
|
||||
// 9. Compute the verification string by hashing S using the algorithm
|
||||
|
@ -767,7 +766,7 @@ public final class EntityCapsManager extends Manager {
|
|||
return new CapsVersionAndHash(version, hash);
|
||||
}
|
||||
|
||||
private static void formFieldValuesToCaps(List<CharSequence> i, StringBuilder sb) {
|
||||
private static void formFieldValuesToCaps(List<? extends CharSequence> i, StringBuilder sb) {
|
||||
SortedSet<CharSequence> fvs = new TreeSet<>();
|
||||
fvs.addAll(i);
|
||||
for (CharSequence fv : fvs) {
|
||||
|
|
|
@ -24,7 +24,8 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
|||
import org.jivesoftware.smack.packet.StanzaError;
|
||||
|
||||
import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
|
@ -188,13 +189,8 @@ public abstract class AdHocCommand {
|
|||
* @return the form of the current stage to fill out or the result of the
|
||||
* execution.
|
||||
*/
|
||||
public Form getForm() {
|
||||
if (data.getForm() == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return new Form(data.getForm());
|
||||
}
|
||||
public DataForm getForm() {
|
||||
return data.getForm();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,8 +201,8 @@ public abstract class AdHocCommand {
|
|||
* @param form the form of the current stage to fill out or the result of the
|
||||
* execution.
|
||||
*/
|
||||
protected void setForm(Form form) {
|
||||
data.setForm(form.getDataFormToSend());
|
||||
protected void setForm(DataForm form) {
|
||||
data.setForm(form);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,7 +230,7 @@ public abstract class AdHocCommand {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public abstract void next(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
||||
public abstract void next(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Completes the command execution with the information provided in the
|
||||
|
@ -250,7 +246,7 @@ public abstract class AdHocCommand {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public abstract void complete(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
||||
public abstract void complete(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Goes to the previous stage. The requester is asking to re-send the
|
||||
|
|
|
@ -52,7 +52,8 @@ import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider;
|
|||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
|
@ -462,7 +463,8 @@ public final class AdHocCommandManager extends Manager {
|
|||
|
||||
if (Action.next.equals(action)) {
|
||||
command.incrementStage();
|
||||
command.next(new Form(requestData.getForm()));
|
||||
DataForm dataForm = requestData.getForm();
|
||||
command.next(new FillableForm(dataForm));
|
||||
if (command.isLastStage()) {
|
||||
// If it is the last stage then the command is
|
||||
// completed
|
||||
|
@ -475,7 +477,8 @@ public final class AdHocCommandManager extends Manager {
|
|||
}
|
||||
else if (Action.complete.equals(action)) {
|
||||
command.incrementStage();
|
||||
command.complete(new Form(requestData.getForm()));
|
||||
DataForm dataForm = requestData.getForm();
|
||||
command.complete(new FillableForm(dataForm));
|
||||
response.setStatus(Status.completed);
|
||||
// Remove the completed session
|
||||
executingCommands.remove(sessionId);
|
||||
|
|
|
@ -23,7 +23,8 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
|||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
||||
import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
|
@ -80,8 +81,8 @@ public class RemoteCommand extends AdHocCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void complete(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.complete, form);
|
||||
public void complete(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.complete, form.getDataFormToSubmit());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -100,13 +101,13 @@ public class RemoteCommand extends AdHocCommand {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public void execute(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.execute, form);
|
||||
public void execute(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.execute, form.getDataFormToSubmit());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.next, form);
|
||||
public void next(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
executeAction(Action.next, form.getDataFormToSubmit());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +131,7 @@ public class RemoteCommand extends AdHocCommand {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
private void executeAction(Action action, Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
private void executeAction(Action action, DataForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
// TODO: Check that all the required fields of the form were filled, if
|
||||
// TODO: not throw the corresponding exception. This will make a faster response,
|
||||
// TODO: since the request is stopped before it's sent.
|
||||
|
@ -140,10 +141,7 @@ public class RemoteCommand extends AdHocCommand {
|
|||
data.setNode(getNode());
|
||||
data.setSessionID(sessionID);
|
||||
data.setAction(action);
|
||||
|
||||
if (form != null) {
|
||||
data.setForm(form.getDataFormToSend());
|
||||
}
|
||||
data.setForm(form);
|
||||
|
||||
AdHocCommandData responseData = null;
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
* Copyright © 2014-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -42,7 +42,7 @@ public abstract class AbstractNodeInformationProvider implements NodeInformation
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionElement> getNodePacketExtensions() {
|
||||
public List<? extends ExtensionElement> getNodePacketExtensions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,5 +68,5 @@ public interface NodeInformationProvider {
|
|||
*
|
||||
* @return a list of the stanza extensions defined in the node.
|
||||
*/
|
||||
List<ExtensionElement> getNodePacketExtensions();
|
||||
List<? extends ExtensionElement> getNodePacketExtensions();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software, 2018-2019 Florian Schmaus.
|
||||
* Copyright 2003-2007 Jive Software, 2018-2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -38,10 +38,10 @@ import org.jivesoftware.smack.XMPPConnectionRegistry;
|
|||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
|
||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.StanzaError;
|
||||
import org.jivesoftware.smack.util.CollectionUtil;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
|
@ -88,7 +88,7 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
private static final Map<XMPPConnection, ServiceDiscoveryManager> instances = new WeakHashMap<>();
|
||||
|
||||
private final Set<String> features = new HashSet<>();
|
||||
private DataForm extendedInfo = null;
|
||||
private List<DataForm> extendedInfos = new ArrayList<>(2);
|
||||
private final Map<String, NodeInformationProvider> nodeInformationProviders = new ConcurrentHashMap<>();
|
||||
|
||||
// Create a new ServiceDiscoveryManager on every established connection
|
||||
|
@ -307,9 +307,8 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
for (String feature : getFeatures()) {
|
||||
response.addFeature(feature);
|
||||
}
|
||||
if (extendedInfo != null) {
|
||||
response.addExtension(extendedInfo);
|
||||
}
|
||||
|
||||
response.addExtensions(extendedInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,25 +426,59 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
* configure the extended info before logging to the server so that the
|
||||
* information is already available if it is required upon login.
|
||||
*
|
||||
* @param info TODO javadoc me please
|
||||
* the data form that contains the extend service discovery
|
||||
* @param info the data form that contains the extend service discovery
|
||||
* information.
|
||||
* @deprecated use {@link #addExtendedInfo(DataForm)} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.5
|
||||
@Deprecated
|
||||
public synchronized void setExtendedInfo(DataForm info) {
|
||||
extendedInfo = info;
|
||||
// Notify others of a state change of SDM. In order to keep the state consistent, this
|
||||
// method is synchronized
|
||||
renewEntityCapsVersion();
|
||||
addExtendedInfo(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128).
|
||||
* Registers extended discovery information of this XMPP entity. When this
|
||||
* client is queried for its information this data form will be returned as
|
||||
* specified by XEP-0128.
|
||||
* <p>
|
||||
*
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0128.html">XEP-128: Service Discovery Extensions</a>
|
||||
* @return the data form
|
||||
* Since no stanza is actually sent to the server it is safe to perform this
|
||||
* operation before logging to the server. In fact, you may want to
|
||||
* configure the extended info before logging to the server so that the
|
||||
* information is already available if it is required upon login.
|
||||
*
|
||||
* @param extendedInfo the data form that contains the extend service discovery information.
|
||||
* @return the old data form which got replaced (if any)
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public DataForm getExtendedInfo() {
|
||||
return extendedInfo;
|
||||
public DataForm addExtendedInfo(DataForm extendedInfo) {
|
||||
String formType = extendedInfo.getFormType();
|
||||
StringUtils.requireNotNullNorEmpty(formType, "The data form must have a form type set");
|
||||
|
||||
DataForm removedDataForm;
|
||||
synchronized (this) {
|
||||
removedDataForm = DataForm.remove(extendedInfos, formType);
|
||||
|
||||
extendedInfos.add(extendedInfo);
|
||||
|
||||
// Notify others of a state change of SDM. In order to keep the state consistent, this
|
||||
// method is synchronized
|
||||
renewEntityCapsVersion();
|
||||
}
|
||||
return removedDataForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the extended discovery information of the given form type.
|
||||
*
|
||||
* @param formType the type of the data form with the extended discovery information to remove.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public synchronized void removeExtendedInfo(String formType) {
|
||||
DataForm removedForm = DataForm.remove(extendedInfos, formType);
|
||||
if (removedForm != null) {
|
||||
renewEntityCapsVersion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -454,13 +487,21 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
*
|
||||
* @return the data form as List of PacketExtensions
|
||||
*/
|
||||
public List<ExtensionElement> getExtendedInfoAsList() {
|
||||
List<ExtensionElement> res = null;
|
||||
if (extendedInfo != null) {
|
||||
res = new ArrayList<>(1);
|
||||
res.add(extendedInfo);
|
||||
}
|
||||
return res;
|
||||
public synchronized List<DataForm> getExtendedInfo() {
|
||||
return CollectionUtil.newListWith(extendedInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data form as List of PacketExtensions, or null if no data form is set.
|
||||
* This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider)
|
||||
*
|
||||
* @return the data form as List of PacketExtensions
|
||||
* @deprecated use {@link #getExtendedInfo()} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.5
|
||||
@Deprecated
|
||||
public List<DataForm> getExtendedInfoAsList() {
|
||||
return getExtendedInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -471,10 +512,13 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
* operation before logging to the server.
|
||||
*/
|
||||
public synchronized void removeExtendedInfo() {
|
||||
extendedInfo = null;
|
||||
// Notify others of a state change of SDM. In order to keep the state consistent, this
|
||||
// method is synchronized
|
||||
renewEntityCapsVersion();
|
||||
int extendedInfosCount = extendedInfos.size();
|
||||
extendedInfos.clear();
|
||||
if (extendedInfosCount > 0) {
|
||||
// Notify others of a state change of SDM. In order to keep the state consistent, this
|
||||
// method is synchronized
|
||||
renewEntityCapsVersion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2006 Jive Software.
|
||||
*
|
||||
* 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.filetransfer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
import org.jivesoftware.smackx.bytestreams.ibb.packet.Open;
|
||||
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
|
||||
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
|
||||
/**
|
||||
* The fault tolerant negotiator takes two stream negotiators, the primary and the secondary
|
||||
* negotiator. If the primary negotiator fails during the stream negotiation process, the second
|
||||
* negotiator is used.
|
||||
*/
|
||||
public class FaultTolerantNegotiator extends StreamNegotiator {
|
||||
|
||||
private final StreamNegotiator primaryNegotiator;
|
||||
private final StreamNegotiator secondaryNegotiator;
|
||||
|
||||
public FaultTolerantNegotiator(XMPPConnection connection, StreamNegotiator primary,
|
||||
StreamNegotiator secondary) {
|
||||
super(connection);
|
||||
this.primaryNegotiator = primary;
|
||||
this.secondaryNegotiator = secondary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newStreamInitiation(Jid from, String streamID) {
|
||||
primaryNegotiator.newStreamInitiation(from, streamID);
|
||||
secondaryNegotiator.newStreamInitiation(from, streamID);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream negotiateIncomingStream(Stanza streamInitiation) {
|
||||
throw new UnsupportedOperationException("Negotiation only handled by create incoming " +
|
||||
"stream method.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream createIncomingStream(final StreamInitiation initiation) throws SmackException, XMPPErrorException, InterruptedException {
|
||||
// This could be either an xep47 ibb 'open' iq or an xep65 streamhost iq
|
||||
IQ initiationSet = initiateIncomingStream(connection(), initiation);
|
||||
|
||||
StreamNegotiator streamNegotiator = determineNegotiator(initiationSet);
|
||||
|
||||
return streamNegotiator.negotiateIncomingStream(initiationSet);
|
||||
}
|
||||
|
||||
private StreamNegotiator determineNegotiator(Stanza streamInitiation) {
|
||||
if (streamInitiation instanceof Bytestream) {
|
||||
return primaryNegotiator;
|
||||
} else if (streamInitiation instanceof Open) {
|
||||
return secondaryNegotiator;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown stream initiation type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream createOutgoingStream(String streamID, Jid initiator, Jid target)
|
||||
throws SmackException, XMPPException, InterruptedException {
|
||||
OutputStream stream;
|
||||
try {
|
||||
stream = primaryNegotiator.createOutgoingStream(streamID, initiator, target);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
stream = secondaryNegotiator.createOutgoingStream(streamID, initiator, target);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getNamespaces() {
|
||||
String[] primary = primaryNegotiator.getNamespaces();
|
||||
String[] secondary = secondaryNegotiator.getNamespaces();
|
||||
|
||||
String[] namespaces = new String[primary.length + secondary.length];
|
||||
System.arraycopy(primary, 0, namespaces, 0, primary.length);
|
||||
System.arraycopy(secondary, 0, namespaces, primary.length, secondary.length);
|
||||
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
}
|
|
@ -42,6 +42,7 @@ import org.jivesoftware.smackx.filetransfer.FileTransferException.NoAcceptableTr
|
|||
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoStreamMethodsOfferedException;
|
||||
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.ListSingleFormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
@ -189,7 +190,7 @@ public final class FileTransferNegotiator extends Manager {
|
|||
public StreamNegotiator selectStreamNegotiator(
|
||||
FileTransferRequest request) throws NotConnectedException, NoStreamMethodsOfferedException, NoAcceptableTransferMechanisms, InterruptedException {
|
||||
StreamInitiation si = request.getStreamInitiation();
|
||||
FormField streamMethodField = getStreamMethodField(si
|
||||
ListSingleFormField streamMethodField = getStreamMethodField(si
|
||||
.getFeatureNegotiationForm());
|
||||
|
||||
if (streamMethodField == null) {
|
||||
|
@ -216,11 +217,11 @@ public final class FileTransferNegotiator extends Manager {
|
|||
return selectedStreamNegotiator;
|
||||
}
|
||||
|
||||
private static FormField getStreamMethodField(DataForm form) {
|
||||
return form.getField(STREAM_DATA_FIELD_NAME);
|
||||
private static ListSingleFormField getStreamMethodField(DataForm form) {
|
||||
return (ListSingleFormField) form.getField(STREAM_DATA_FIELD_NAME);
|
||||
}
|
||||
|
||||
private StreamNegotiator getNegotiator(final FormField field)
|
||||
private StreamNegotiator getNegotiator(final ListSingleFormField field)
|
||||
throws NoAcceptableTransferMechanisms {
|
||||
String variable;
|
||||
boolean isByteStream = false;
|
||||
|
@ -239,12 +240,7 @@ public final class FileTransferNegotiator extends Manager {
|
|||
throw new FileTransferException.NoAcceptableTransferMechanisms();
|
||||
}
|
||||
|
||||
if (isByteStream && isIBB) {
|
||||
return new FaultTolerantNegotiator(connection(),
|
||||
byteStreamTransferManager,
|
||||
inbandTransferManager);
|
||||
}
|
||||
else if (isByteStream) {
|
||||
if (isByteStream) {
|
||||
return byteStreamTransferManager;
|
||||
}
|
||||
else {
|
||||
|
@ -355,11 +351,7 @@ public final class FileTransferNegotiator extends Manager {
|
|||
throw new FileTransferException.NoAcceptableTransferMechanisms();
|
||||
}
|
||||
|
||||
if (isByteStream && isIBB) {
|
||||
return new FaultTolerantNegotiator(connection(),
|
||||
byteStreamTransferManager, inbandTransferManager);
|
||||
}
|
||||
else if (isByteStream) {
|
||||
if (isByteStream) {
|
||||
return byteStreamTransferManager;
|
||||
}
|
||||
else {
|
||||
|
@ -368,16 +360,15 @@ public final class FileTransferNegotiator extends Manager {
|
|||
}
|
||||
|
||||
private static DataForm createDefaultInitiationForm() {
|
||||
DataForm form = new DataForm(DataForm.Type.form);
|
||||
FormField.Builder fieldBuilder = FormField.builder();
|
||||
fieldBuilder.setFieldName(STREAM_DATA_FIELD_NAME)
|
||||
.setType(FormField.Type.list_single);
|
||||
DataForm.Builder form = DataForm.builder()
|
||||
.setType(DataForm.Type.form);
|
||||
ListSingleFormField.Builder fieldBuilder = FormField.listSingleBuilder(STREAM_DATA_FIELD_NAME);
|
||||
|
||||
if (!IBB_ONLY) {
|
||||
fieldBuilder.addOption(Bytestream.NAMESPACE);
|
||||
}
|
||||
fieldBuilder.addOption(DataPacketExtension.NAMESPACE);
|
||||
form.addField(fieldBuilder.build());
|
||||
return form;
|
||||
return form.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,8 +90,8 @@ public class IBBTransferNegotiator extends StreamNegotiator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String[] getNamespaces() {
|
||||
return new String[] { DataPacketExtension.NAMESPACE };
|
||||
public String getNamespace() {
|
||||
return DataPacketExtension.NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -88,8 +88,8 @@ public class Socks5TransferNegotiator extends StreamNegotiator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String[] getNamespaces() {
|
||||
return new String[] { Bytestream.NAMESPACE };
|
||||
public String getNamespace() {
|
||||
return Bytestream.NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.jivesoftware.smack.util.EventManger.Callback;
|
|||
|
||||
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.ListSingleFormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
@ -69,33 +70,31 @@ public abstract class StreamNegotiator extends Manager {
|
|||
* initiator.
|
||||
*
|
||||
* @param streamInitiationOffer The offer from the stream initiator to connect for a stream.
|
||||
* @param namespaces The namespace that relates to the accepted means of transfer.
|
||||
* @param namespace The namespace that relates to the accepted means of transfer.
|
||||
* @return The response to be forwarded to the initiator.
|
||||
*/
|
||||
protected static StreamInitiation createInitiationAccept(
|
||||
StreamInitiation streamInitiationOffer, String[] namespaces) {
|
||||
StreamInitiation streamInitiationOffer, String namespace) {
|
||||
StreamInitiation response = new StreamInitiation();
|
||||
response.setTo(streamInitiationOffer.getFrom());
|
||||
response.setFrom(streamInitiationOffer.getTo());
|
||||
response.setType(IQ.Type.result);
|
||||
response.setStanzaId(streamInitiationOffer.getStanzaId());
|
||||
|
||||
DataForm form = new DataForm(DataForm.Type.submit);
|
||||
FormField.Builder field = FormField.builder(
|
||||
DataForm.Builder form = DataForm.builder();
|
||||
ListSingleFormField.Builder field = FormField.listSingleBuilder(
|
||||
FileTransferNegotiator.STREAM_DATA_FIELD_NAME);
|
||||
for (String namespace : namespaces) {
|
||||
field.addValue(namespace);
|
||||
}
|
||||
field.setValue(namespace);
|
||||
form.addField(field.build());
|
||||
|
||||
response.setFeatureNegotiationForm(form);
|
||||
response.setFeatureNegotiationForm(form.build());
|
||||
return response;
|
||||
}
|
||||
|
||||
protected final IQ initiateIncomingStream(final XMPPConnection connection, StreamInitiation initiation)
|
||||
throws NoResponseException, XMPPErrorException, NotConnectedException {
|
||||
final StreamInitiation response = createInitiationAccept(initiation,
|
||||
getNamespaces());
|
||||
getNamespace());
|
||||
|
||||
newStreamInitiation(initiation.getFrom(), initiation.getSessionID());
|
||||
|
||||
|
@ -182,7 +181,7 @@ public abstract class StreamNegotiator extends Manager {
|
|||
* @return Returns the XMPP namespace reserved for this particular type of
|
||||
* file transfer.
|
||||
*/
|
||||
public abstract String[] getNamespaces();
|
||||
public abstract String getNamespace();
|
||||
|
||||
public static void signal(String eventKey, IQ eventValue) {
|
||||
initationSetEvents.signalEvent(eventKey, eventValue);
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.formtypes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class FormFieldRegistry {
|
||||
|
||||
private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>();
|
||||
|
||||
private static final Map<String, FormField.Type> LOOKASIDE_REGISTRY = new HashMap<>();
|
||||
|
||||
private static final Map<String, String> FIELD_NAME_TO_FORM_TYPE = new HashMap<>();
|
||||
|
||||
static {
|
||||
register(FormField.FORM_TYPE, FormField.Type.hidden);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
public static synchronized void register(DataForm dataForm) {
|
||||
// TODO: Also allow forms of type 'result'?
|
||||
if (dataForm.getType() != DataForm.Type.form) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
String formType = null;
|
||||
TextSingleFormField hiddenFormTypeField = dataForm.getHiddenFormTypeField();
|
||||
if (hiddenFormTypeField != null) {
|
||||
formType = hiddenFormTypeField.getValue();
|
||||
}
|
||||
|
||||
for (FormField formField : dataForm.getFields()) {
|
||||
// Note that we can compare here by reference equality to skip the hidden form type field.
|
||||
if (formField == hiddenFormTypeField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String fieldName = formField.getFieldName();
|
||||
FormField.Type type = formField.getType();
|
||||
register(formType, fieldName, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void register(String formType, String fieldName, FormField.Type type) {
|
||||
if (formType == null) {
|
||||
FormFieldInformation formFieldInformation = lookup(fieldName);
|
||||
if (formFieldInformation != null) {
|
||||
if (Objects.equals(formType, formFieldInformation.formType)
|
||||
&& type.equals(formFieldInformation.formFieldType)) {
|
||||
// The field is already registered, nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
String message = "There is already a field with the name'" + fieldName
|
||||
+ "' registered with the field type '" + formFieldInformation.formFieldType
|
||||
+ "', while this tries to register the field with the type '" + type + '\'';
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
LOOKASIDE_REGISTRY.put(fieldName, type);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType);
|
||||
if (fieldNameToType == null) {
|
||||
fieldNameToType = new HashMap<>();
|
||||
REGISTRY.put(formType, fieldNameToType);
|
||||
} else {
|
||||
FormField.Type previousType = fieldNameToType.get(fieldName);
|
||||
if (previousType != null && previousType != type) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
fieldNameToType.put(fieldName, type);
|
||||
|
||||
FIELD_NAME_TO_FORM_TYPE.put(fieldName, formType);
|
||||
}
|
||||
|
||||
public static synchronized void register(String fieldName, FormField.Type type) {
|
||||
FormField.Type previousType = LOOKASIDE_REGISTRY.get(fieldName);
|
||||
if (previousType != null) {
|
||||
if (previousType == type) {
|
||||
// Nothing to do here.
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("There is already a field with the name '" + fieldName
|
||||
+ "' registered with type " + previousType
|
||||
+ ", while trying to register this field with type '" + type + "'");
|
||||
}
|
||||
|
||||
LOOKASIDE_REGISTRY.put(fieldName, type);
|
||||
}
|
||||
|
||||
public static synchronized FormField.Type lookup(String formType, String fieldName) {
|
||||
if (formType != null) {
|
||||
Map<String, FormField.Type> fieldNameToTypeMap = REGISTRY.get(formType);
|
||||
if (fieldNameToTypeMap != null) {
|
||||
FormField.Type type = fieldNameToTypeMap.get(fieldName);
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName);
|
||||
if (formType != null) {
|
||||
FormField.Type type = lookup(formType, fieldName);
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to lookaside registry.
|
||||
return LOOKASIDE_REGISTRY.get(fieldName);
|
||||
}
|
||||
|
||||
public static synchronized FormFieldInformation lookup(String fieldName) {
|
||||
String formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName);
|
||||
FormField.Type type = lookup(formType, fieldName);
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FormFieldInformation(type, formType);
|
||||
}
|
||||
|
||||
public static final class FormFieldInformation {
|
||||
public final FormField.Type formFieldType;
|
||||
public final String formType;
|
||||
|
||||
|
||||
private FormFieldInformation(FormField.Type formFieldType, String formType) {
|
||||
this.formFieldType = formFieldType;
|
||||
this.formType = formType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smack's implementation of XEP-0068: Field Standardization for Data Forms.
|
||||
*/
|
||||
package org.jivesoftware.smackx.formtypes;
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez 2019 Florian Schmaus
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,13 +16,9 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.geoloc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.jivesoftware.smack.AsyncButOrdered;
|
||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
|
@ -30,31 +26,26 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
|
||||
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
|
||||
import org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider;
|
||||
import org.jivesoftware.smackx.pep.PepListener;
|
||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||
import org.jivesoftware.smackx.pep.PepManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
|
||||
import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProviderManager;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* Entry point for Smacks API for XEP-0080: User Location.
|
||||
* <br>
|
||||
* To publish a UserLocation, please use {@link #sendGeolocation(GeoLocation)} method. This will publish the node.
|
||||
* To publish a UserLocation, please use {@link #publishGeoLocation(GeoLocation)} method. This will publish the node.
|
||||
* <br>
|
||||
* To stop publishing a UserLocation, please use {@link #stopPublishingGeolocation()} method. This will send a disble publishing signal.
|
||||
* <br>
|
||||
* To add a {@link GeoLocationListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(GeoLocationListener)} method.
|
||||
* To add a {@link PepEventListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(PepEventListener)} method.
|
||||
* <br>
|
||||
* To link a GeoLocation with {@link Message}, use `message.addExtension(geoLocation)`.
|
||||
* <br>
|
||||
|
@ -65,16 +56,10 @@ import org.jxmpp.jid.Jid;
|
|||
*/
|
||||
public final class GeoLocationManager extends Manager {
|
||||
|
||||
public static final String GEOLOCATION_NODE = "http://jabber.org/protocol/geoloc";
|
||||
public static final String GEOLOCATION_NOTIFY = GEOLOCATION_NODE + "+notify";
|
||||
public static final String GEOLOCATION_NODE = GeoLocation.NAMESPACE;
|
||||
|
||||
private static final Map<XMPPConnection, GeoLocationManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
private static boolean ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = true;
|
||||
|
||||
private final Set<GeoLocationListener> geoLocationListeners = new CopyOnWriteArraySet<>();
|
||||
private final AsyncButOrdered<BareJid> asyncButOrdered = new AsyncButOrdered<BareJid>();
|
||||
private final ServiceDiscoveryManager serviceDiscoveryManager;
|
||||
private final PepManager pepManager;
|
||||
|
||||
static {
|
||||
|
@ -108,31 +93,6 @@ public final class GeoLocationManager extends Manager {
|
|||
private GeoLocationManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
pepManager = PepManager.getInstanceFor(connection);
|
||||
pepManager.addPepListener(new PepListener() {
|
||||
|
||||
@Override
|
||||
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
|
||||
if (!GEOLOCATION_NODE.equals(event.getEvent().getNode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BareJid contact = from.asBareJid();
|
||||
asyncButOrdered.performAsyncButOrdered(contact, () -> {
|
||||
ItemsExtension itemsExtension = (ItemsExtension) event.getEvent();
|
||||
List<ExtensionElement> items = itemsExtension.getExtensions();
|
||||
@SuppressWarnings("unchecked")
|
||||
PayloadItem<GeoLocation> payload = (PayloadItem<GeoLocation>) items.get(0);
|
||||
GeoLocation geoLocation = payload.getPayload();
|
||||
for (GeoLocationListener listener : geoLocationListeners) {
|
||||
listener.onGeoLocationUpdated(contact, geoLocation, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||
if (ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT) {
|
||||
enableUserLocationNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendGeoLocationToJid(GeoLocation geoLocation, Jid jid) throws InterruptedException,
|
||||
|
@ -160,18 +120,18 @@ public final class GeoLocationManager extends Manager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Send geolocation through the PubSub node.
|
||||
* Publish the user's geographic location through the Personal Eventing Protocol (PEP).
|
||||
*
|
||||
* @param geoLocation TODO javadoc me please
|
||||
* @param geoLocation the geographic location to publish.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws XMPPErrorException if there was an XMPP error returned.
|
||||
* @throws NoResponseException if there was no response from the remote entity.
|
||||
* @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
|
||||
*/
|
||||
public void sendGeolocation(GeoLocation geoLocation)
|
||||
public void publishGeoLocation(GeoLocation geoLocation)
|
||||
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
|
||||
pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem<GeoLocation>(geoLocation));
|
||||
pepManager.publish(GEOLOCATION_NODE, new PayloadItem<GeoLocation>(geoLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,25 +145,14 @@ public final class GeoLocationManager extends Manager {
|
|||
*/
|
||||
public void stopPublishingGeolocation()
|
||||
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
|
||||
pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem<GeoLocation>(GeoLocation.EMPTY_GEO_LOCATION));
|
||||
pepManager.publish(GEOLOCATION_NODE, new PayloadItem<GeoLocation>(GeoLocation.EMPTY_GEO_LOCATION));
|
||||
}
|
||||
|
||||
public static void setGeoLocationNotificationsEnabledByDefault(boolean bool) {
|
||||
ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = bool;
|
||||
public boolean addGeoLocationListener(PepEventListener<GeoLocation> listener) {
|
||||
return pepManager.addPepEventListener(GEOLOCATION_NODE, GeoLocation.class, listener);
|
||||
}
|
||||
|
||||
public void enableUserLocationNotifications() {
|
||||
serviceDiscoveryManager.addFeature(GEOLOCATION_NOTIFY);
|
||||
}
|
||||
|
||||
public void disableGeoLocationNotifications() {
|
||||
serviceDiscoveryManager.removeFeature(GEOLOCATION_NOTIFY);
|
||||
}
|
||||
|
||||
public boolean addGeoLocationListener(GeoLocationListener geoLocationListener) {
|
||||
return geoLocationListeners.add(geoLocationListener);
|
||||
}
|
||||
public boolean removeGeoLocationListener(GeoLocationListener geoLocationListener) {
|
||||
return geoLocationListeners.remove(geoLocationListener);
|
||||
public boolean removeGeoLocationListener(PepEventListener<GeoLocation> listener) {
|
||||
return pepManager.removePepEventListener(listener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019 Florian Schmaus
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -19,14 +19,13 @@ package org.jivesoftware.smackx.geoloc.packet;
|
|||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.EqualsUtil;
|
||||
import org.jivesoftware.smack.util.HashCode;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
|
@ -50,8 +49,6 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
|
||||
public static final GeoLocation EMPTY_GEO_LOCATION = GeoLocation.builder().build();
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GeoLocation.class.getName());
|
||||
|
||||
private final Double accuracy;
|
||||
private final Double alt;
|
||||
private final Double altAccuracy;
|
||||
|
@ -77,50 +74,31 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
private final String tzo;
|
||||
private final URI uri;
|
||||
|
||||
private GeoLocation(Double accuracy, Double alt, Double altAccuracy, String area, Double bearing, String building, String country,
|
||||
String countryCode, String datum, String description, Double error, String floor, Double lat,
|
||||
String locality, Double lon, String postalcode, String region, String room, Double speed,
|
||||
String street, String text, Date timestamp, String tzo, URI uri) {
|
||||
this.accuracy = accuracy;
|
||||
this.alt = alt;
|
||||
this.altAccuracy = altAccuracy;
|
||||
this.area = area;
|
||||
this.bearing = bearing;
|
||||
this.building = building;
|
||||
this.country = country;
|
||||
this.countryCode = countryCode;
|
||||
|
||||
// If datum is not included, receiver MUST assume WGS84; receivers MUST implement WGS84; senders MAY use another
|
||||
// datum, but it is not recommended.
|
||||
|
||||
if (StringUtils.isNullOrEmpty(datum)) {
|
||||
datum = "WGS84";
|
||||
}
|
||||
|
||||
this.datum = datum;
|
||||
this.description = description;
|
||||
|
||||
// error element is deprecated in favor of accuracy
|
||||
if (accuracy != null) {
|
||||
error = null;
|
||||
LOGGER.log(Level.WARNING,
|
||||
"Error and accuracy set. Ignoring error as it is deprecated in favor of accuracy");
|
||||
}
|
||||
|
||||
this.error = error;
|
||||
this.floor = floor;
|
||||
this.lat = lat;
|
||||
this.locality = locality;
|
||||
this.lon = lon;
|
||||
this.postalcode = postalcode;
|
||||
this.region = region;
|
||||
this.room = room;
|
||||
this.speed = speed;
|
||||
this.street = street;
|
||||
this.text = text;
|
||||
this.timestamp = timestamp;
|
||||
this.tzo = tzo;
|
||||
this.uri = uri;
|
||||
private GeoLocation(Builder builder) {
|
||||
accuracy = builder.accuracy;
|
||||
alt = builder.alt;
|
||||
altAccuracy = builder.altAccuracy;
|
||||
area = builder.area;
|
||||
bearing = builder.bearing;
|
||||
building = builder.building;
|
||||
country = builder.country;
|
||||
countryCode = builder.countryCode;
|
||||
datum = builder.datum;
|
||||
description = builder.description;
|
||||
error = builder.error;
|
||||
floor = builder.floor;
|
||||
lat = builder.lat;
|
||||
locality = builder.locality;
|
||||
lon = builder.lon;
|
||||
postalcode = builder.postalcode;
|
||||
region = builder.region;
|
||||
room = builder.room;
|
||||
speed = builder.speed;
|
||||
street = builder.street;
|
||||
text = builder.text;
|
||||
timestamp = builder.timestamp;
|
||||
tzo = builder.tzo;
|
||||
uri = builder.uri;
|
||||
}
|
||||
|
||||
public Double getAccuracy() {
|
||||
|
@ -163,6 +141,13 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error.
|
||||
*
|
||||
* @return the error.
|
||||
* @deprecated use {@link #getAccuracy()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Double getError() {
|
||||
return error;
|
||||
}
|
||||
|
@ -266,6 +251,70 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
return NAMESPACE;
|
||||
}
|
||||
|
||||
private final HashCode.Cache hashCodeCache = new HashCode.Cache();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCodeCache.getHashCode(c ->
|
||||
c
|
||||
.append(accuracy)
|
||||
.append(alt)
|
||||
.append(altAccuracy)
|
||||
.append(area)
|
||||
.append(bearing)
|
||||
.append(building)
|
||||
.append(country)
|
||||
.append(countryCode)
|
||||
.append(datum)
|
||||
.append(description)
|
||||
.append(error)
|
||||
.append(floor)
|
||||
.append(lat)
|
||||
.append(locality)
|
||||
.append(lon)
|
||||
.append(postalcode)
|
||||
.append(region)
|
||||
.append(room)
|
||||
.append(speed)
|
||||
.append(street)
|
||||
.append(text)
|
||||
.append(timestamp)
|
||||
.append(tzo)
|
||||
.append(uri)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return EqualsUtil.equals(this, obj, (e, o) -> {
|
||||
e
|
||||
.append(accuracy, o.accuracy)
|
||||
.append(altAccuracy, o.altAccuracy)
|
||||
.append(area, o.area)
|
||||
.append(bearing, o.bearing)
|
||||
.append(building, o.building)
|
||||
.append(country, o.country)
|
||||
.append(countryCode, o.countryCode)
|
||||
.append(datum, o.datum)
|
||||
.append(description, o.description)
|
||||
.append(error, o.error)
|
||||
.append(floor, o.floor)
|
||||
.append(lat, o.lat)
|
||||
.append(locality, o.locality)
|
||||
.append(lon, o.lon)
|
||||
.append(postalcode, o.postalcode)
|
||||
.append(region, o.region)
|
||||
.append(room, o.room)
|
||||
.append(speed, o.speed)
|
||||
.append(street, o.street)
|
||||
.append(text, o.text)
|
||||
.append(timestamp, o.timestamp)
|
||||
.append(tzo, o.tzo)
|
||||
.append(uri, o.uri)
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link Builder}.
|
||||
* @return Builder
|
||||
|
@ -318,7 +367,11 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
private String building;
|
||||
private String country;
|
||||
private String countryCode;
|
||||
private String datum;
|
||||
|
||||
// If datum is not included, receiver MUST assume WGS84; receivers MUST implement WGS84; senders MAY use another
|
||||
// datum, but it is not recommended.
|
||||
private String datum = "WGS84";
|
||||
|
||||
private String description;
|
||||
private Double error;
|
||||
private String floor;
|
||||
|
@ -453,7 +506,9 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
*
|
||||
* @param error error in arc minutes
|
||||
* @return Builder
|
||||
* @deprecated use {@link #setAccuracy(Double)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder setError(Double error) {
|
||||
this.error = error;
|
||||
return this;
|
||||
|
@ -610,10 +665,7 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
|
|||
* @return GeoLocation
|
||||
*/
|
||||
public GeoLocation build() {
|
||||
|
||||
return new GeoLocation(accuracy, alt, altAccuracy, area, bearing, building, country, countryCode, datum, description,
|
||||
error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp,
|
||||
tzo, uri);
|
||||
return new GeoLocation(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019 Florian Schmaus
|
||||
* Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -79,7 +79,7 @@ public class GeoLocationProvider extends ExtensionElementProvider<GeoLocation> {
|
|||
builder.setDescription(parser.nextText());
|
||||
break;
|
||||
case "error":
|
||||
builder.setError(ParserUtils.getDoubleFromNextText(parser));
|
||||
parseError(builder, parser);
|
||||
break;
|
||||
case "floor":
|
||||
builder.setFloor(parser.nextText());
|
||||
|
@ -136,6 +136,12 @@ public class GeoLocationProvider extends ExtensionElementProvider<GeoLocation> {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void parseError(GeoLocation.Builder builder, XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
double error = ParserUtils.getDoubleFromNextText(parser);
|
||||
builder.setError(error);
|
||||
}
|
||||
|
||||
public static class GeoLocationFormFieldChildElementProvider extends FormFieldChildElementProvider<GeoLocation> {
|
||||
|
||||
public static final GeoLocationFormFieldChildElementProvider INSTANCE = new GeoLocationFormFieldChildElementProvider();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub.
|
||||
* Copyright 2018 Paul Schaub, 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,9 +20,10 @@ import org.jivesoftware.smack.packet.Message;
|
|||
|
||||
import org.jivesoftware.smackx.mood.element.MoodElement;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
||||
public interface MoodListener {
|
||||
|
||||
void onMoodUpdated(BareJid jid, Message message, MoodElement moodElement);
|
||||
void onMoodUpdated(EntityBareJid from, MoodElement moodElement, String id, Message message);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub.
|
||||
* Copyright 2018 Paul Schaub, 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,12 +16,9 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.mood;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jivesoftware.smack.AsyncButOrdered;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
|
@ -29,21 +26,13 @@ import org.jivesoftware.smack.XMPPException;
|
|||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.provider.ProviderManager;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.mood.element.MoodConcretisation;
|
||||
import org.jivesoftware.smackx.mood.element.MoodElement;
|
||||
import org.jivesoftware.smackx.mood.provider.MoodConcretisationProvider;
|
||||
import org.jivesoftware.smackx.pep.PepListener;
|
||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||
import org.jivesoftware.smackx.pep.PepManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
||||
/**
|
||||
* Entry point for Smacks API for XEP-0107: User Mood.
|
||||
|
@ -51,8 +40,8 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
* To set a mood, please use one of the {@link #setMood(Mood)} methods. This will publish the users mood to a pubsub
|
||||
* node.<br>
|
||||
* <br>
|
||||
* In order to get updated about other users moods, register a {@link MoodListener} at
|
||||
* {@link #addMoodListener(MoodListener)}. That listener will get updated about any incoming mood updates of contacts.<br>
|
||||
* In order to get updated about other users moods, register a {@link PepEventListener} at
|
||||
* {@link #addMoodListener(PepEventListener)}. That listener will get updated about any incoming mood updates of contacts.<br>
|
||||
* <br>
|
||||
* To stop publishing the users mood, refer to {@link #clearMood()}.<br>
|
||||
* <br>
|
||||
|
@ -68,39 +57,15 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
public final class MoodManager extends Manager {
|
||||
|
||||
public static final String MOOD_NODE = "http://jabber.org/protocol/mood";
|
||||
public static final String MOOD_NOTIFY = MOOD_NODE + "+notify";
|
||||
|
||||
private static final Map<XMPPConnection, MoodManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
private final Set<MoodListener> moodListeners = new HashSet<>();
|
||||
private final AsyncButOrdered<BareJid> asyncButOrdered = new AsyncButOrdered<>();
|
||||
private PubSubManager pubSubManager;
|
||||
private final PepManager pepManager;
|
||||
|
||||
private MoodManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MOOD_NOTIFY);
|
||||
PepManager.getInstanceFor(connection).addPepListener(new PepListener() {
|
||||
@Override
|
||||
public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) {
|
||||
if (!MOOD_NODE.equals(event.getEvent().getNode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BareJid contact = from.asBareJid();
|
||||
asyncButOrdered.performAsyncButOrdered(contact, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ItemsExtension items = (ItemsExtension) event.getExtensions().get(0);
|
||||
PayloadItem<?> payload = (PayloadItem<?>) items.getItems().get(0);
|
||||
MoodElement mood = (MoodElement) payload.getPayload();
|
||||
|
||||
for (MoodListener listener : moodListeners) {
|
||||
listener.onMoodUpdated(contact, message, mood);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
pepManager = PepManager.getInstanceFor(connection);
|
||||
}
|
||||
|
||||
public static synchronized MoodManager getInstanceFor(XMPPConnection connection) {
|
||||
|
@ -147,12 +112,7 @@ public final class MoodManager extends Manager {
|
|||
private void publishMood(MoodElement moodElement)
|
||||
throws SmackException.NotLoggedInException, InterruptedException, PubSubException.NotALeafNodeException,
|
||||
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
if (pubSubManager == null) {
|
||||
pubSubManager = PubSubManager.getInstanceFor(getAuthenticatedConnectionOrThrow(), connection().getUser().asBareJid());
|
||||
}
|
||||
|
||||
LeafNode node = pubSubManager.getOrCreateLeafNode(MOOD_NODE);
|
||||
node.publish(new PayloadItem<>(moodElement));
|
||||
pepManager.publish(MOOD_NODE, new PayloadItem<>(moodElement));
|
||||
}
|
||||
|
||||
private static MoodElement buildMood(Mood mood, MoodConcretisation concretisation, String text) {
|
||||
|
@ -170,11 +130,11 @@ public final class MoodManager extends Manager {
|
|||
message.addExtension(element);
|
||||
}
|
||||
|
||||
public synchronized void addMoodListener(MoodListener listener) {
|
||||
moodListeners.add(listener);
|
||||
public boolean addMoodListener(PepEventListener<MoodElement> listener) {
|
||||
return pepManager.addPepEventListener(MOOD_NODE, MoodElement.class, listener);
|
||||
}
|
||||
|
||||
public synchronized void removeMoodListener(MoodListener listener) {
|
||||
moodListeners.remove(listener);
|
||||
public boolean removeMoodListener(PepEventListener<MoodElement> listener) {
|
||||
return pepManager.removePepEventListener(listener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015 Florian Schmaus
|
||||
* Copyright 2015-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -23,17 +23,18 @@ import java.util.List;
|
|||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupportedException;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.form.FilledForm;
|
||||
import org.jivesoftware.smackx.xdata.form.Form;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.jxmpp.jid.util.JidUtil;
|
||||
|
||||
/**
|
||||
* Multi-User Chat configuration form manager is used to fill out and submit a {@link Form} used to
|
||||
* Multi-User Chat configuration form manager is used to fill out and submit a {@link FilledForm} used to
|
||||
* configure rooms.
|
||||
* <p>
|
||||
* Room configuration needs either be done right after the room is created and still locked. Or at
|
||||
|
@ -43,12 +44,17 @@ import org.jxmpp.jid.util.JidUtil;
|
|||
* </p>
|
||||
* <p>
|
||||
* The manager may not provide all possible configuration options. If you want direct access to the
|
||||
* configuraiton form, use {@link MultiUserChat#getConfigurationForm()} and
|
||||
* {@link MultiUserChat#sendConfigurationForm(Form)}.
|
||||
* configuration form, use {@link MultiUserChat#getConfigurationForm()} and
|
||||
* {@link MultiUserChat#sendConfigurationForm(FillableForm)}.
|
||||
* </p>
|
||||
*/
|
||||
public class MucConfigFormManager {
|
||||
/**
|
||||
|
||||
private static final String HASH_ROOMCONFIG = "#roomconfig";
|
||||
|
||||
public static final String FORM_TYPE = MultiUserChatConstants.NAMESPACE + HASH_ROOMCONFIG;
|
||||
|
||||
/**
|
||||
* The constant String {@value}.
|
||||
*
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0045.html#owner">XEP-0045 § 10. Owner Use Cases</a>
|
||||
|
@ -73,7 +79,7 @@ public class MucConfigFormManager {
|
|||
public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret";
|
||||
|
||||
private final MultiUserChat multiUserChat;
|
||||
private final Form answerForm;
|
||||
private final FillableForm answerForm;
|
||||
private final List<Jid> owners;
|
||||
|
||||
/**
|
||||
|
@ -94,20 +100,13 @@ public class MucConfigFormManager {
|
|||
|
||||
// Set the answer form
|
||||
Form configForm = multiUserChat.getConfigurationForm();
|
||||
this.answerForm = configForm.createAnswerForm();
|
||||
// Add the default answers to the form to submit
|
||||
for (FormField field : configForm.getFields()) {
|
||||
if (field.getType() == FormField.Type.hidden
|
||||
|| StringUtils.isNullOrEmpty(field.getVariable())) {
|
||||
continue;
|
||||
}
|
||||
answerForm.setDefaultAnswer(field.getVariable());
|
||||
}
|
||||
this.answerForm = configForm.getFillableForm();
|
||||
|
||||
// Set the local variables according to the fields found in the answer form
|
||||
if (answerForm.hasField(MUC_ROOMCONFIG_ROOMOWNERS)) {
|
||||
FormField roomOwnersFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMOWNERS);
|
||||
if (roomOwnersFormField != null) {
|
||||
// Set 'owners' to the currently configured owners
|
||||
List<CharSequence> ownerStrings = answerForm.getField(MUC_ROOMCONFIG_ROOMOWNERS).getValues();
|
||||
List<? extends CharSequence> ownerStrings = roomOwnersFormField.getValues();
|
||||
owners = new ArrayList<>(ownerStrings.size());
|
||||
JidUtil.jidsFrom(ownerStrings, owners, null);
|
||||
}
|
||||
|
@ -244,7 +243,7 @@ public class MucConfigFormManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Submit the configuration as {@link Form} to the room.
|
||||
* Submit the configuration as {@link FilledForm} to the room.
|
||||
*
|
||||
* @throws NoResponseException if there was no response from the room.
|
||||
* @throws XMPPErrorException if there was an XMPP error returned.
|
||||
|
|
|
@ -133,7 +133,7 @@ public final class MucEnterConfiguration {
|
|||
*
|
||||
* @param presenceBuilderConsumer a consumer which will be passed the presence build.
|
||||
* @return a reference to this builder.
|
||||
* @since 4.5
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public Builder withPresence(Consumer<? super PresenceBuilder> presenceBuilderConsumer) {
|
||||
presenceBuilderConsumer.accept(joinPresenceBuilder);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
* Copyright 2003-2007 Jive Software. 2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -75,8 +75,10 @@ import org.jivesoftware.smackx.muc.packet.MUCItem;
|
|||
import org.jivesoftware.smackx.muc.packet.MUCOwner;
|
||||
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
||||
import org.jivesoftware.smackx.muc.packet.MUCUser.Status;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.form.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
|
@ -149,7 +151,6 @@ public class MultiUserChat {
|
|||
|
||||
private String subject;
|
||||
private EntityFullJid myRoomJid;
|
||||
private boolean joined = false;
|
||||
private StanzaCollector messageCollector;
|
||||
|
||||
MultiUserChat(XMPPConnection connection, EntityBareJid room, MultiUserChatManager multiUserChatManager) {
|
||||
|
@ -233,10 +234,13 @@ public class MultiUserChat {
|
|||
occupantsMap.remove(from);
|
||||
MUCUser mucUser = MUCUser.from(packet);
|
||||
if (mucUser != null && mucUser.hasStatus()) {
|
||||
if (isUserStatusModification) {
|
||||
userHasLeft();
|
||||
}
|
||||
// Fire events according to the received presence code
|
||||
checkPresenceCode(
|
||||
mucUser.getStatus(),
|
||||
presence.getFrom().equals(myRoomJID),
|
||||
isUserStatusModification,
|
||||
mucUser,
|
||||
from);
|
||||
} else {
|
||||
|
@ -246,6 +250,27 @@ public class MultiUserChat {
|
|||
listener.left(from);
|
||||
}
|
||||
}
|
||||
|
||||
Destroy destroy = mucUser.getDestroy();
|
||||
// The room has been destroyed.
|
||||
if (destroy != null) {
|
||||
EntityBareJid alternateMucJid = destroy.getJid();
|
||||
final MultiUserChat alternateMuc;
|
||||
if (alternateMucJid == null) {
|
||||
alternateMuc = null;
|
||||
} else {
|
||||
alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid);
|
||||
}
|
||||
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.roomDestroyed(alternateMuc, destroy.getReason());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isUserStatusModification) {
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.removed(mucUser, presence);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -383,8 +408,6 @@ public class MultiUserChat {
|
|||
Resourcepart receivedNickname = presence.getFrom().getResourceOrThrow();
|
||||
setNickname(receivedNickname);
|
||||
|
||||
joined = true;
|
||||
|
||||
// Update the list of joined rooms
|
||||
multiUserChatManager.addJoinedRoom(room);
|
||||
return presence;
|
||||
|
@ -436,7 +459,7 @@ public class MultiUserChat {
|
|||
public synchronized MucCreateConfigFormHandle create(Resourcepart nickname) throws NoResponseException,
|
||||
XMPPErrorException, InterruptedException, MucAlreadyJoinedException,
|
||||
NotConnectedException, MissingMucCreationAcknowledgeException, NotAMucServiceException {
|
||||
if (joined) {
|
||||
if (isJoined()) {
|
||||
throw new MucAlreadyJoinedException();
|
||||
}
|
||||
|
||||
|
@ -491,7 +514,7 @@ public class MultiUserChat {
|
|||
*/
|
||||
public synchronized MucCreateConfigFormHandle createOrJoin(MucEnterConfiguration mucEnterConfiguration)
|
||||
throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException {
|
||||
if (joined) {
|
||||
if (isJoined()) {
|
||||
throw new MucAlreadyJoinedException();
|
||||
}
|
||||
|
||||
|
@ -512,8 +535,8 @@ public class MultiUserChat {
|
|||
* instant room, use {@link #makeInstant()}.
|
||||
* <p>
|
||||
* For advanced configuration options, use {@link MultiUserChat#getConfigurationForm()}, get the answer form with
|
||||
* {@link Form#createAnswerForm()}, fill it out and send it back to the room with
|
||||
* {@link MultiUserChat#sendConfigurationForm(Form)}.
|
||||
* {@link Form#getFillableForm()}, fill it out and send it back to the room with
|
||||
* {@link MultiUserChat#sendConfigurationForm(FillableForm)}.
|
||||
* </p>
|
||||
*/
|
||||
public class MucCreateConfigFormHandle {
|
||||
|
@ -531,7 +554,7 @@ public class MultiUserChat {
|
|||
*/
|
||||
public void makeInstant() throws NoResponseException, XMPPErrorException, NotConnectedException,
|
||||
InterruptedException {
|
||||
sendConfigurationForm(new Form(DataForm.Type.submit));
|
||||
sendConfigurationForm(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -662,7 +685,7 @@ public class MultiUserChat {
|
|||
throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException, NotAMucServiceException {
|
||||
// If we've already joined the room, leave it before joining under a new
|
||||
// nickname.
|
||||
if (joined) {
|
||||
if (isJoined()) {
|
||||
try {
|
||||
leaveSync();
|
||||
}
|
||||
|
@ -680,7 +703,7 @@ public class MultiUserChat {
|
|||
* @return true if currently in the multi user chat room.
|
||||
*/
|
||||
public boolean isJoined() {
|
||||
return joined;
|
||||
return myRoomJid != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -716,10 +739,6 @@ public class MultiUserChat {
|
|||
// "if (!joined) return" because it should be always be possible to leave the room in case the instance's
|
||||
// state does not reflect the actual state.
|
||||
|
||||
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
|
||||
// throw.
|
||||
userHasLeft();
|
||||
|
||||
final EntityFullJid myRoomJid = this.myRoomJid;
|
||||
if (myRoomJid == null) {
|
||||
throw new MucNotJoinedException(this);
|
||||
|
@ -741,6 +760,10 @@ public class MultiUserChat {
|
|||
)
|
||||
);
|
||||
|
||||
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
|
||||
// throw.
|
||||
userHasLeft();
|
||||
|
||||
Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
|
||||
|
||||
return reflectedLeavePresence;
|
||||
|
@ -783,7 +806,8 @@ public class MultiUserChat {
|
|||
iq.setType(IQ.Type.get);
|
||||
|
||||
IQ answer = connection.createStanzaCollectorAndSend(iq).nextResultOrThrow();
|
||||
return Form.getFormFrom(answer);
|
||||
DataForm dataForm = DataForm.from(answer, MucConfigFormManager.FORM_TYPE);
|
||||
return new Form(dataForm);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -796,11 +820,19 @@ public class MultiUserChat {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public void sendConfigurationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public void sendConfigurationForm(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
final DataForm dataForm;
|
||||
if (form != null) {
|
||||
dataForm = form.getDataFormToSubmit();
|
||||
} else {
|
||||
// Instant room, cf. XEP-0045 § 10.1.2
|
||||
dataForm = DataForm.builder().build();
|
||||
}
|
||||
|
||||
MUCOwner iq = new MUCOwner();
|
||||
iq.setTo(room);
|
||||
iq.setType(IQ.Type.set);
|
||||
iq.addExtension(form.getDataFormToSend());
|
||||
iq.addExtension(dataForm);
|
||||
|
||||
connection.createStanzaCollectorAndSend(iq).nextResultOrThrow();
|
||||
}
|
||||
|
@ -828,7 +860,8 @@ public class MultiUserChat {
|
|||
reg.setTo(room);
|
||||
|
||||
IQ result = connection.createStanzaCollectorAndSend(reg).nextResultOrThrow();
|
||||
return Form.getFormFrom(result);
|
||||
DataForm dataForm = DataForm.from(result);
|
||||
return new Form(dataForm);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -848,11 +881,11 @@ public class MultiUserChat {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public void sendRegistrationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public void sendRegistrationForm(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
Registration reg = new Registration();
|
||||
reg.setType(IQ.Type.set);
|
||||
reg.setTo(room);
|
||||
reg.addExtension(form.getDataFormToSend());
|
||||
reg.addExtension(form.getDataFormToSubmit());
|
||||
|
||||
connection.createStanzaCollectorAndSend(reg).nextResultOrThrow();
|
||||
}
|
||||
|
@ -1135,7 +1168,7 @@ public class MultiUserChat {
|
|||
Objects.requireNonNull(nickname, "Nickname must not be null or blank.");
|
||||
// Check that we already have joined the room before attempting to change the
|
||||
// nickname.
|
||||
if (!joined) {
|
||||
if (!isJoined()) {
|
||||
throw new MucNotJoinedException(this);
|
||||
}
|
||||
final EntityFullJid jid = JidCreate.entityFullFrom(room, nickname);
|
||||
|
@ -1178,11 +1211,6 @@ public class MultiUserChat {
|
|||
throw new MucNotJoinedException(this);
|
||||
}
|
||||
|
||||
// Check that we already have joined the room before attempting to change the
|
||||
// availability status.
|
||||
if (!joined) {
|
||||
throw new MucNotJoinedException(this);
|
||||
}
|
||||
// We change the availability status by sending a presence packet to the room with the
|
||||
// new presence status and mode
|
||||
Presence joinPresence = connection.getStanzaFactory().buildPresenceStanza()
|
||||
|
@ -1231,19 +1259,17 @@ public class MultiUserChat {
|
|||
* @since 4.1
|
||||
*/
|
||||
public void requestVoice() throws NotConnectedException, InterruptedException {
|
||||
DataForm form = new DataForm(DataForm.Type.submit);
|
||||
FormField.Builder formTypeField = FormField.builder(FormField.FORM_TYPE);
|
||||
formTypeField.addValue(MUCInitialPresence.NAMESPACE + "#request");
|
||||
form.addField(formTypeField.build());
|
||||
FormField.Builder requestVoiceField = FormField.builder("muc#role");
|
||||
requestVoiceField.setType(FormField.Type.text_single);
|
||||
DataForm.Builder form = DataForm.builder()
|
||||
.setFormType(MUCInitialPresence.NAMESPACE + "#request");
|
||||
|
||||
TextSingleFormField.Builder requestVoiceField = FormField.textSingleBuilder("muc#role");
|
||||
requestVoiceField.setLabel("Requested role");
|
||||
requestVoiceField.addValue("participant");
|
||||
requestVoiceField.setValue("participant");
|
||||
form.addField(requestVoiceField.build());
|
||||
|
||||
Message message = connection.getStanzaFactory().buildMessageStanza()
|
||||
.to(room)
|
||||
.addExtension(form)
|
||||
.addExtension(form.build())
|
||||
.build();
|
||||
connection.sendStanza(message);
|
||||
}
|
||||
|
@ -2119,7 +2145,7 @@ public class MultiUserChat {
|
|||
// to call leave() in order to resync the state. And leave() requires the nickname to send the unsubscribe
|
||||
// presence.
|
||||
occupantsMap.clear();
|
||||
joined = false;
|
||||
myRoomJid = null;
|
||||
// Update the list of joined rooms
|
||||
multiUserChatManager.removeJoinedRoom(room);
|
||||
removeConnectionCallbacks();
|
||||
|
@ -2437,9 +2463,6 @@ public class MultiUserChat {
|
|||
if (statusCodes.contains(Status.KICKED_307)) {
|
||||
// Check if this occupant was kicked
|
||||
if (isUserModification) {
|
||||
// Reset occupant information.
|
||||
userHasLeft();
|
||||
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.kicked(mucUser.getItem().getActor(), mucUser.getItem().getReason());
|
||||
}
|
||||
|
@ -2454,15 +2477,9 @@ public class MultiUserChat {
|
|||
if (statusCodes.contains(Status.BANNED_301)) {
|
||||
// Check if this occupant was banned
|
||||
if (isUserModification) {
|
||||
joined = false;
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.banned(mucUser.getItem().getActor(), mucUser.getItem().getReason());
|
||||
}
|
||||
|
||||
// Reset occupant information.
|
||||
occupantsMap.clear();
|
||||
myRoomJid = null;
|
||||
userHasLeft();
|
||||
}
|
||||
else {
|
||||
for (ParticipantStatusListener listener : participantStatusListeners) {
|
||||
|
@ -2474,15 +2491,9 @@ public class MultiUserChat {
|
|||
if (statusCodes.contains(Status.REMOVED_AFFIL_CHANGE_321)) {
|
||||
// Check if this occupant's membership was revoked
|
||||
if (isUserModification) {
|
||||
joined = false;
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.membershipRevoked();
|
||||
}
|
||||
|
||||
// Reset occupant information.
|
||||
occupantsMap.clear();
|
||||
myRoomJid = null;
|
||||
userHasLeft();
|
||||
}
|
||||
}
|
||||
// A occupant has changed his nickname in the room
|
||||
|
@ -2491,18 +2502,6 @@ public class MultiUserChat {
|
|||
listener.nicknameChanged(from, mucUser.getItem().getNick());
|
||||
}
|
||||
}
|
||||
// The room has been destroyed.
|
||||
if (mucUser.getDestroy() != null) {
|
||||
MultiUserChat alternateMUC = multiUserChatManager.getMultiUserChat(mucUser.getDestroy().getJid());
|
||||
for (UserStatusListener listener : userStatusListeners) {
|
||||
listener.roomDestroyed(alternateMUC, mucUser.getDestroy().getReason());
|
||||
}
|
||||
|
||||
// Reset occupant information.
|
||||
occupantsMap.clear();
|
||||
myRoomJid = null;
|
||||
userHasLeft();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Aditya Borikar.
|
||||
* Copyright 2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,13 +14,10 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.geoloc;
|
||||
package org.jivesoftware.smackx.muc;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
|
||||
public class MultiUserChatConstants {
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
public static final String NAMESPACE = "http://jabber.org/protocol/muc";
|
||||
|
||||
public interface GeoLocationListener {
|
||||
void onGeoLocationUpdated(BareJid jid, GeoLocation geoLocation, Message message);
|
||||
}
|
|
@ -25,8 +25,8 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
@ -130,7 +130,7 @@ public class RoomInfo {
|
|||
/**
|
||||
* The rooms extended configuration form;
|
||||
*/
|
||||
private final Form form;
|
||||
private final DataForm form;
|
||||
|
||||
RoomInfo(DiscoverInfo info) {
|
||||
final Jid from = info.getFrom();
|
||||
|
@ -166,7 +166,7 @@ public class RoomInfo {
|
|||
URL logs = null;
|
||||
String pubsub = null;
|
||||
// Get the information based on the discovered extended information
|
||||
form = Form.getFormFrom(info);
|
||||
form = DataForm.from(info);
|
||||
if (form != null) {
|
||||
FormField descField = form.getField("muc#roominfo_description");
|
||||
if (descField != null && !descField.getValues().isEmpty()) {
|
||||
|
@ -191,7 +191,7 @@ public class RoomInfo {
|
|||
|
||||
FormField contactJidField = form.getField("muc#roominfo_contactjid");
|
||||
if (contactJidField != null && !contactJidField.getValues().isEmpty()) {
|
||||
List<CharSequence> contactJidValues = contactJidField.getValues();
|
||||
List<? extends CharSequence> contactJidValues = contactJidField.getValues();
|
||||
contactJid = JidUtil.filterEntityBareJidList(JidUtil.jidSetFrom(contactJidValues));
|
||||
}
|
||||
|
||||
|
@ -420,7 +420,7 @@ public class RoomInfo {
|
|||
* href="http://xmpp.org/extensions/xep-0045.html#disco-roominfo">XEP-45:
|
||||
* Multi User Chat - 6.5 Querying for Room Information</a>
|
||||
*/
|
||||
public Form getForm() {
|
||||
public DataForm getForm() {
|
||||
return form;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,21 @@
|
|||
|
||||
package org.jivesoftware.smackx.muc;
|
||||
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
|
||||
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* A listener that is fired anytime your participant's status in a room is changed, such as the
|
||||
* user being kicked, banned, or granted admin permissions or the room is destroyed.
|
||||
* A listener that is fired anytime your participant's status in a room is changed, such as the user being kicked,
|
||||
* banned, or granted admin permissions or the room is destroyed.
|
||||
* <p>
|
||||
* Note that the methods {@link #kicked(Jid, String)}, {@link #banned(Jid, String)} and
|
||||
* {@link #roomDestroyed(MultiUserChat, String)} will be called before the generic {@link #removed(MUCUser, Presence)}
|
||||
* callback will be invoked. The generic {@link #removed(MUCUser, Presence)} callback will be invoked every time the user
|
||||
* was removed from the MUC involuntarily. It is hence the recommended callback to listen for and act upon.
|
||||
* </p>
|
||||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
|
@ -33,6 +43,7 @@ public interface UserStatusListener {
|
|||
*
|
||||
* @param actor the moderator that kicked your user from the room (e.g. user@host.org).
|
||||
* @param reason the reason provided by the actor to kick you from the room.
|
||||
* @see #removed(MUCUser, Presence)
|
||||
*/
|
||||
void kicked(Jid actor, String reason);
|
||||
|
||||
|
@ -58,10 +69,21 @@ public interface UserStatusListener {
|
|||
*
|
||||
* @param actor the administrator that banned your user (e.g. user@host.org).
|
||||
* @param reason the reason provided by the administrator to banned you.
|
||||
* @see #removed(MUCUser, Presence)
|
||||
*/
|
||||
void banned(Jid actor, String reason);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Called when a user is involuntarily removed from the room.
|
||||
*
|
||||
* @param mucUser the optional muc#user extension element
|
||||
* @param presence the carrier presence
|
||||
* @since 4.4.0
|
||||
*/
|
||||
default void removed(MUCUser mucUser, Presence presence) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when an administrator grants your user membership to the room. This means that you
|
||||
* will be able to join the members-only room.
|
||||
*
|
||||
|
@ -128,6 +150,7 @@ public interface UserStatusListener {
|
|||
*
|
||||
* @param alternateMUC an alternate MultiUserChat, may be null.
|
||||
* @param reason the reason why the room was closed, may be null.
|
||||
* @see #removed(MUCUser, Presence)
|
||||
*/
|
||||
void roomDestroyed(MultiUserChat alternateMUC, String reason);
|
||||
|
||||
|
|
|
@ -410,6 +410,7 @@ public class MUCUser implements ExtensionElement {
|
|||
public static final Status NEW_NICKNAME_303 = Status.create(303);
|
||||
public static final Status KICKED_307 = Status.create(307);
|
||||
public static final Status REMOVED_AFFIL_CHANGE_321 = Status.create(321);
|
||||
public static final Status REMOVED_FOR_TECHNICAL_REASONS_333 = Status.create(333);
|
||||
|
||||
private final Integer code;
|
||||
|
||||
|
@ -419,10 +420,14 @@ public class MUCUser implements ExtensionElement {
|
|||
}
|
||||
|
||||
public static Status create(Integer i) {
|
||||
Status status = statusMap.get(i);
|
||||
if (status == null) {
|
||||
status = new Status(i);
|
||||
statusMap.put(i, status);
|
||||
Status status;
|
||||
// TODO: Use computeIfAbsent once Smack's minimum required Android SDK level is 24 or higher.
|
||||
synchronized (statusMap) {
|
||||
status = statusMap.get(i);
|
||||
if (status == null) {
|
||||
status = new Status(i);
|
||||
statusMap.put(i, status);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
* Copyright 2003-2007 Jive Software, 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -43,7 +43,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
|||
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
||||
import org.jivesoftware.smackx.offline.packet.OfflineMessageInfo;
|
||||
import org.jivesoftware.smackx.offline.packet.OfflineMessageRequest;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
/**
|
||||
* The OfflineMessageManager helps manage offline messages even before the user has sent an
|
||||
|
@ -115,12 +115,12 @@ public final class OfflineMessageManager extends Manager {
|
|||
*/
|
||||
public int getMessageCount() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
DiscoverInfo info = serviceDiscoveryManager.discoverInfo(null, namespace);
|
||||
Form extendedInfo = Form.getFormFrom(info);
|
||||
if (extendedInfo != null) {
|
||||
String value = extendedInfo.getField("number_of_messages").getFirstValue();
|
||||
return Integer.parseInt(value);
|
||||
DataForm dataForm = DataForm.from(info, namespace);
|
||||
if (dataForm == null) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
String numberOfMessagesString = dataForm.getField("number_of_messages").getFirstValue();
|
||||
return Integer.parseInt(numberOfMessagesString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019 Aditya Borikar.
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,15 +14,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.usertune;
|
||||
package org.jivesoftware.smackx.pep;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
import org.jivesoftware.smackx.usertune.element.UserTuneElement;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
public interface PepEventListener<E extends ExtensionElement> {
|
||||
|
||||
public interface UserTuneListener {
|
||||
void onPepEvent(EntityBareJid from, E event, String id, Message carrierMessage);
|
||||
|
||||
void onUserTuneUpdated(BareJid jid, Message message, UserTuneElement userTuneElement);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software, 2015-2019 Florian Schmaus
|
||||
* Copyright 2003-2007 Jive Software, 2015-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,10 +17,13 @@
|
|||
|
||||
package org.jivesoftware.smackx.pep;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.AsyncButOrdered;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
|
@ -30,20 +33,26 @@ import org.jivesoftware.smack.StanzaListener;
|
|||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.jidtype.AbstractJidTypeFilter.JidType;
|
||||
import org.jivesoftware.smack.filter.jidtype.FromJidTypeFilter;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.CollectionUtil;
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.Item;
|
||||
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubFeature;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||
import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter;
|
||||
import org.jivesoftware.smackx.pubsub.filter.EventItemsExtensionFilter;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
@ -70,6 +79,8 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
*/
|
||||
public final class PepManager extends Manager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PepManager.class.getName());
|
||||
|
||||
private static final Map<XMPPConnection, PepManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
public static synchronized PepManager getInstanceFor(XMPPConnection connection) {
|
||||
|
@ -81,16 +92,25 @@ public final class PepManager extends Manager {
|
|||
return pepManager;
|
||||
}
|
||||
|
||||
private static final StanzaFilter FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER = new AndFilter(
|
||||
new FromJidTypeFilter(JidType.BareJid),
|
||||
EventExtensionFilter.INSTANCE);
|
||||
// TODO: Ideally PepManager would re-use PubSubManager for this. But the functionality in PubSubManager does not yet
|
||||
// exist.
|
||||
private static final StanzaFilter PEP_EVENTS_FILTER = new AndFilter(
|
||||
MessageTypeFilter.NORMAL_OR_HEADLINE,
|
||||
FromJidTypeFilter.ENTITY_BARE_JID,
|
||||
EventItemsExtensionFilter.INSTANCE);
|
||||
|
||||
private final Set<PepListener> pepListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private final AsyncButOrdered<EntityBareJid> asyncButOrdered = new AsyncButOrdered<>();
|
||||
|
||||
private final ServiceDiscoveryManager serviceDiscoveryManager;
|
||||
|
||||
private final PubSubManager pepPubSubManager;
|
||||
|
||||
private final MultiMap<String, PepEventListenerCoupling<? extends ExtensionElement>> pepEventListeners = new MultiMap<>();
|
||||
|
||||
private final Map<PepEventListener<?>, PepEventListenerCoupling<?>> listenerToCouplingMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new PEP exchange manager.
|
||||
*
|
||||
|
@ -98,6 +118,10 @@ public final class PepManager extends Manager {
|
|||
*/
|
||||
private PepManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
|
||||
serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||
pepPubSubManager = PubSubManager.getInstanceFor(connection, null);
|
||||
|
||||
StanzaListener packetListener = new StanzaListener() {
|
||||
@Override
|
||||
public void processStanza(Stanza stanza) {
|
||||
|
@ -106,20 +130,118 @@ public final class PepManager extends Manager {
|
|||
assert event != null;
|
||||
final EntityBareJid from = message.getFrom().asEntityBareJidIfPossible();
|
||||
assert from != null;
|
||||
|
||||
asyncButOrdered.performAsyncButOrdered(from, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ItemsExtension itemsExtension = (ItemsExtension) event.getEvent();
|
||||
String node = itemsExtension.getNode();
|
||||
|
||||
for (PepListener listener : pepListeners) {
|
||||
listener.eventReceived(from, event, message);
|
||||
}
|
||||
|
||||
List<PepEventListenerCoupling<? extends ExtensionElement>> nodeListeners;
|
||||
synchronized (pepEventListeners) {
|
||||
nodeListeners = pepEventListeners.getAll(node);
|
||||
if (nodeListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a copy of the list. Note that it is important to do this within the synchronized
|
||||
// block.
|
||||
nodeListeners = CollectionUtil.newListWith(nodeListeners);
|
||||
}
|
||||
|
||||
for (PepEventListenerCoupling<? extends ExtensionElement> listener : nodeListeners) {
|
||||
// TODO: Can there be more than one item?
|
||||
List<? extends NamedElement> items = itemsExtension.getItems();
|
||||
for (NamedElement namedElementItem : items) {
|
||||
Item item = (Item) namedElementItem;
|
||||
String id = item.getId();
|
||||
@SuppressWarnings("unchecked")
|
||||
PayloadItem<ExtensionElement> payloadItem = (PayloadItem<ExtensionElement>) item;
|
||||
ExtensionElement payload = payloadItem.getPayload();
|
||||
|
||||
listener.invoke(from, payload, id, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// TODO Add filter to check if from supports PubSub as per xep163 2 2.4
|
||||
connection.addSyncStanzaListener(packetListener, FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER);
|
||||
connection.addSyncStanzaListener(packetListener, PEP_EVENTS_FILTER);
|
||||
}
|
||||
|
||||
pepPubSubManager = PubSubManager.getInstanceFor(connection, null);
|
||||
private static final class PepEventListenerCoupling<E extends ExtensionElement> {
|
||||
private final String node;
|
||||
private final Class<E> extensionElementType;
|
||||
private final PepEventListener<E> pepEventListener;
|
||||
|
||||
private PepEventListenerCoupling(String node, Class<E> extensionElementType,
|
||||
PepEventListener<E> pepEventListener) {
|
||||
this.node = node;
|
||||
this.extensionElementType = extensionElementType;
|
||||
this.pepEventListener = pepEventListener;
|
||||
}
|
||||
|
||||
private void invoke(EntityBareJid from, ExtensionElement payload, String id, Message carrierMessage) {
|
||||
if (!extensionElementType.isInstance(payload)) {
|
||||
LOGGER.warning("Ignoring " + payload + " from " + carrierMessage + " as it is not of type "
|
||||
+ extensionElementType);
|
||||
return;
|
||||
}
|
||||
|
||||
E extensionElementPayload = extensionElementType.cast(payload);
|
||||
pepEventListener.onPepEvent(from, extensionElementPayload, id, carrierMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public <E extends ExtensionElement> boolean addPepEventListener(String node, Class<E> extensionElementType,
|
||||
PepEventListener<E> pepEventListener) {
|
||||
PepEventListenerCoupling<E> pepEventListenerCoupling = new PepEventListenerCoupling<>(node,
|
||||
extensionElementType, pepEventListener);
|
||||
|
||||
synchronized (pepEventListeners) {
|
||||
if (listenerToCouplingMap.containsKey(pepEventListener)) {
|
||||
return false;
|
||||
}
|
||||
listenerToCouplingMap.put(pepEventListener, pepEventListenerCoupling);
|
||||
/*
|
||||
* TODO: Replace the above with the below using putIfAbsent() if Smack's minimum required Android SDK level
|
||||
* is 24 or higher. PepEventListenerCoupling<?> currentPepEventListenerCoupling =
|
||||
* listenerToCouplingMap.putIfAbsent(pepEventListener, pepEventListenerCoupling); if
|
||||
* (currentPepEventListenerCoupling != null) { return false; }
|
||||
*/
|
||||
|
||||
boolean listenerForNodeExisted = pepEventListeners.put(node, pepEventListenerCoupling);
|
||||
if (!listenerForNodeExisted) {
|
||||
serviceDiscoveryManager.addFeature(node + PubSubManager.PLUS_NOTIFY);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removePepEventListener(PepEventListener<?> pepEventListener) {
|
||||
synchronized (pepEventListeners) {
|
||||
PepEventListenerCoupling<?> pepEventListenerCoupling = listenerToCouplingMap.remove(pepEventListener);
|
||||
if (pepEventListenerCoupling == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String node = pepEventListenerCoupling.node;
|
||||
|
||||
boolean mappingExisted = pepEventListeners.removeOne(node, pepEventListenerCoupling);
|
||||
assert mappingExisted;
|
||||
|
||||
if (!pepEventListeners.containsKey(pepEventListenerCoupling.node)) {
|
||||
// This was the last listener for the node. Remove the +notify feature.
|
||||
serviceDiscoveryManager.removeFeature(node + PubSubManager.PLUS_NOTIFY);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public PubSubManager getPepPubSubManager() {
|
||||
|
@ -127,8 +249,7 @@ public final class PepManager extends Manager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to PEPs. The listener will be fired anytime PEP events
|
||||
* are received from remote XMPP clients.
|
||||
* Adds a listener to PEPs. The listener will be fired anytime PEP events are received from remote XMPP clients.
|
||||
*
|
||||
* @param pepListener a roster exchange listener.
|
||||
* @return true if pepListener was added.
|
||||
|
@ -176,8 +297,8 @@ public final class PepManager extends Manager {
|
|||
// @formatter:on
|
||||
};
|
||||
|
||||
public boolean isSupported() throws NoResponseException, XMPPErrorException,
|
||||
NotConnectedException, InterruptedException {
|
||||
public boolean isSupported()
|
||||
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
XMPPConnection connection = connection();
|
||||
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||
BareJid localBareJid = connection.getUser().asBareJid();
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.util.List;
|
|||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.FilledConfigureForm;
|
||||
|
||||
/**
|
||||
* Represents the <b>configuration</b> element of a PubSub message event which
|
||||
* associates a configuration form to the node which was configured. The form
|
||||
|
@ -30,18 +32,18 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
|||
* @author Robin Collier
|
||||
*/
|
||||
public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension {
|
||||
private ConfigureForm form;
|
||||
private final FilledConfigureForm form;
|
||||
|
||||
public ConfigurationEvent(String nodeId) {
|
||||
super(PubSubElementType.CONFIGURATION, nodeId);
|
||||
this(nodeId, null);
|
||||
}
|
||||
|
||||
public ConfigurationEvent(String nodeId, ConfigureForm configForm) {
|
||||
public ConfigurationEvent(String nodeId, FilledConfigureForm configForm) {
|
||||
super(PubSubElementType.CONFIGURATION, nodeId);
|
||||
form = configForm;
|
||||
}
|
||||
|
||||
public ConfigureForm getConfiguration() {
|
||||
public FilledConfigureForm getConfiguration() {
|
||||
return form;
|
||||
}
|
||||
|
||||
|
@ -50,6 +52,6 @@ public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketE
|
|||
if (getConfiguration() == null)
|
||||
return Collections.emptyList();
|
||||
else
|
||||
return Arrays.asList((ExtensionElement) getConfiguration().getDataFormToSend());
|
||||
return Arrays.asList((ExtensionElement) getConfiguration().getDataForm());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,681 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.pubsub;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
/**
|
||||
* A decorator for a {@link Form} to easily enable reading and updating
|
||||
* of node configuration. All operations read or update the underlying {@link DataForm}.
|
||||
*
|
||||
* <p>Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not
|
||||
* exist, all <b>ConfigureForm.setXXX</b> methods will create the field in the wrapped form
|
||||
* if it does not already exist.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
public class ConfigureForm extends Form {
|
||||
/**
|
||||
* Create a decorator from an existing {@link DataForm} that has been
|
||||
* retrieved from parsing a node configuration request.
|
||||
*
|
||||
* @param configDataForm TODO javadoc me please
|
||||
*/
|
||||
public ConfigureForm(DataForm configDataForm) {
|
||||
super(configDataForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a decorator from an existing {@link Form} for node configuration.
|
||||
* Typically, this can be used to create a decorator for an answer form
|
||||
* by using the result of {@link #createAnswerForm()} as the input parameter.
|
||||
*
|
||||
* @param nodeConfigForm TODO javadoc me please
|
||||
*/
|
||||
public ConfigureForm(Form nodeConfigForm) {
|
||||
super(nodeConfigForm.getDataFormToSend());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new form for configuring a node. This would typically only be used
|
||||
* when creating and configuring a node at the same time via {@link PubSubManager#createNode(String, Form)}, since
|
||||
* configuration of an existing node is typically accomplished by calling {@link LeafNode#getNodeConfiguration()} and
|
||||
* using the resulting form to create a answer form. See {@link #ConfigureForm(Form)}.
|
||||
* @param formType TODO javadoc me please
|
||||
*/
|
||||
public ConfigureForm(DataForm.Type formType) {
|
||||
super(formType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently configured {@link AccessModel}, null if it is not set.
|
||||
*
|
||||
* @return The current {@link AccessModel}
|
||||
*/
|
||||
public AccessModel getAccessModel() {
|
||||
String value = getFieldValue(ConfigureNodeFields.access_model);
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
return AccessModel.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of access model.
|
||||
*
|
||||
* @param accessModel TODO javadoc me please
|
||||
*/
|
||||
public void setAccessModel(AccessModel accessModel) {
|
||||
addField(ConfigureNodeFields.access_model, FormField.Type.list_single);
|
||||
setAnswer(ConfigureNodeFields.access_model.getFieldName(), getListSingle(accessModel.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of an XSL transformation which can be applied to payloads in order to
|
||||
* generate an appropriate message body element.
|
||||
*
|
||||
* @return URL to an XSL
|
||||
*/
|
||||
public String getBodyXSLT() {
|
||||
return getFieldValue(ConfigureNodeFields.body_xslt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL of an XSL transformation which can be applied to payloads in order to
|
||||
* generate an appropriate message body element.
|
||||
*
|
||||
* @param bodyXslt The URL of an XSL
|
||||
*/
|
||||
public void setBodyXSLT(String bodyXslt) {
|
||||
addField(ConfigureNodeFields.body_xslt, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.body_xslt.getFieldName(), bodyXslt);
|
||||
}
|
||||
|
||||
/**
|
||||
* The id's of the child nodes associated with a collection node (both leaf and collection).
|
||||
*
|
||||
* @return list of child nodes.
|
||||
*/
|
||||
public List<String> getChildren() {
|
||||
return getFieldValues(ConfigureNodeFields.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of child node ids that are associated with a collection node.
|
||||
*
|
||||
* @param children TODO javadoc me please
|
||||
*/
|
||||
public void setChildren(List<String> children) {
|
||||
addField(ConfigureNodeFields.children, FormField.Type.text_multi);
|
||||
setAnswer(ConfigureNodeFields.children.getFieldName(), children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the policy that determines who may associate children with the node.
|
||||
*
|
||||
* @return The current policy
|
||||
*/
|
||||
public ChildrenAssociationPolicy getChildrenAssociationPolicy() {
|
||||
String value = getFieldValue(ConfigureNodeFields.children_association_policy);
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
return ChildrenAssociationPolicy.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the policy that determines who may associate children with the node.
|
||||
*
|
||||
* @param policy The policy being set
|
||||
*/
|
||||
public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) {
|
||||
addField(ConfigureNodeFields.children_association_policy, FormField.Type.list_single);
|
||||
List<String> values = new ArrayList<>(1);
|
||||
values.add(policy.toString());
|
||||
setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), values);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of JID's that are on the whitelist that determines who can associate child nodes
|
||||
* with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
|
||||
* {@link ChildrenAssociationPolicy#whitelist}.
|
||||
*
|
||||
* @return List of the whitelist
|
||||
*/
|
||||
public List<String> getChildrenAssociationWhitelist() {
|
||||
return getFieldValues(ConfigureNodeFields.children_association_whitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JID's in the whitelist of users that can associate child nodes with the collection
|
||||
* node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
|
||||
* {@link ChildrenAssociationPolicy#whitelist}.
|
||||
*
|
||||
* @param whitelist The list of JID's
|
||||
*/
|
||||
public void setChildrenAssociationWhitelist(List<String> whitelist) {
|
||||
addField(ConfigureNodeFields.children_association_whitelist, FormField.Type.jid_multi);
|
||||
setAnswer(ConfigureNodeFields.children_association_whitelist.getFieldName(), whitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of child nodes that can be associated with the collection node.
|
||||
*
|
||||
* @return The maximum number of child nodes
|
||||
*/
|
||||
public int getChildrenMax() {
|
||||
return Integer.parseInt(getFieldValue(ConfigureNodeFields.children_max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of child nodes that can be associated with a collection node.
|
||||
*
|
||||
* @param max The maximum number of child nodes.
|
||||
*/
|
||||
public void setChildrenMax(int max) {
|
||||
addField(ConfigureNodeFields.children_max, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.children_max.getFieldName(), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection node which the node is affiliated with.
|
||||
*
|
||||
* @return The collection node id
|
||||
*/
|
||||
public String getCollection() {
|
||||
return getFieldValue(ConfigureNodeFields.collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection node which the node is affiliated with.
|
||||
*
|
||||
* @param collection The node id of the collection node
|
||||
*/
|
||||
public void setCollection(String collection) {
|
||||
addField(ConfigureNodeFields.collection, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.collection.getFieldName(), collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of an XSL transformation which can be applied to the payload
|
||||
* format in order to generate a valid Data Forms result that the client could
|
||||
* display using a generic Data Forms rendering engine.
|
||||
*
|
||||
* @return The URL of an XSL transformation
|
||||
*/
|
||||
public String getDataformXSLT() {
|
||||
return getFieldValue(ConfigureNodeFields.dataform_xslt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of an XSL transformation which can be applied to the payload
|
||||
* format in order to generate a valid Data Forms result that the client could
|
||||
* display using a generic Data Forms rendering engine.
|
||||
*
|
||||
* @param url The URL of an XSL transformation
|
||||
*/
|
||||
public void setDataformXSLT(String url) {
|
||||
addField(ConfigureNodeFields.dataform_xslt, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.dataform_xslt.getFieldName(), url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the node deliver payloads with event notifications.
|
||||
*
|
||||
* @return true if it does, false otherwise
|
||||
*/
|
||||
public boolean isDeliverPayloads() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.deliver_payloads));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the node will deliver payloads with event notifications.
|
||||
*
|
||||
* @param deliver true if the payload will be delivered, false otherwise
|
||||
*/
|
||||
public void setDeliverPayloads(boolean deliver) {
|
||||
addField(ConfigureNodeFields.deliver_payloads, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines who should get replies to items.
|
||||
*
|
||||
* @return Who should get the reply
|
||||
*/
|
||||
public ItemReply getItemReply() {
|
||||
String value = getFieldValue(ConfigureNodeFields.itemreply);
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
return ItemReply.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who should get the replies to items.
|
||||
*
|
||||
* @param reply Defines who should get the reply
|
||||
*/
|
||||
public void setItemReply(ItemReply reply) {
|
||||
addField(ConfigureNodeFields.itemreply, FormField.Type.list_single);
|
||||
setAnswer(ConfigureNodeFields.itemreply.getFieldName(), getListSingle(reply.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is
|
||||
* true.
|
||||
*
|
||||
* @return The maximum number of items to persist
|
||||
*/
|
||||
public int getMaxItems() {
|
||||
return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is
|
||||
* true.
|
||||
*
|
||||
* @param max The maximum number of items to persist
|
||||
*/
|
||||
public void setMaxItems(int max) {
|
||||
addField(ConfigureNodeFields.max_items, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.max_items.getFieldName(), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum payload size in bytes.
|
||||
*
|
||||
* @return The maximum payload size
|
||||
*/
|
||||
public int getMaxPayloadSize() {
|
||||
return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_payload_size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum payload size in bytes.
|
||||
*
|
||||
* @param max The maximum payload size
|
||||
*/
|
||||
public void setMaxPayloadSize(int max) {
|
||||
addField(ConfigureNodeFields.max_payload_size, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.max_payload_size.getFieldName(), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node type.
|
||||
*
|
||||
* @return The node type
|
||||
*/
|
||||
public NodeType getNodeType() {
|
||||
String value = getFieldValue(ConfigureNodeFields.node_type);
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
return NodeType.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node type.
|
||||
*
|
||||
* @param type The node type
|
||||
*/
|
||||
public void setNodeType(NodeType type) {
|
||||
addField(ConfigureNodeFields.node_type, FormField.Type.list_single);
|
||||
setAnswer(ConfigureNodeFields.node_type.getFieldName(), getListSingle(type.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if subscribers should be notified when the configuration changes.
|
||||
*
|
||||
* @return true if they should be notified, false otherwise
|
||||
*/
|
||||
public boolean isNotifyConfig() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when the configuration changes.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyConfig(boolean notify) {
|
||||
addField(ConfigureNodeFields.notify_config, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.notify_config.getFieldName(), notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether subscribers should be notified when the node is deleted.
|
||||
*
|
||||
* @return true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public boolean isNotifyDelete() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_delete));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when the node is deleted.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyDelete(boolean notify) {
|
||||
addField(ConfigureNodeFields.notify_delete, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.notify_delete.getFieldName(), notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether subscribers should be notified when items are deleted
|
||||
* from the node.
|
||||
*
|
||||
* @return true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public boolean isNotifyRetract() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_retract));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when items are deleted
|
||||
* from the node.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyRetract(boolean notify) {
|
||||
addField(ConfigureNodeFields.notify_retract, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.notify_retract.getFieldName(), notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the type of notifications which are sent.
|
||||
*
|
||||
* @return NotificationType for the node configuration
|
||||
* @since 4.3
|
||||
*/
|
||||
public NotificationType getNotificationType() {
|
||||
String value = getFieldValue(ConfigureNodeFields.notification_type);
|
||||
if (value == null)
|
||||
return null;
|
||||
return NotificationType.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the NotificationType for the node.
|
||||
*
|
||||
* @param notificationType The enum representing the possible options
|
||||
* @since 4.3
|
||||
*/
|
||||
public void setNotificationType(NotificationType notificationType) {
|
||||
addField(ConfigureNodeFields.notification_type, FormField.Type.list_single);
|
||||
setAnswer(ConfigureNodeFields.notification_type.getFieldName(), getListSingle(notificationType.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether items should be persisted in the node.
|
||||
*
|
||||
* @return true if items are persisted
|
||||
*/
|
||||
public boolean isPersistItems() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.persist_items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether items should be persisted in the node.
|
||||
*
|
||||
* @param persist true if items should be persisted, false otherwise
|
||||
*/
|
||||
public void setPersistentItems(boolean persist) {
|
||||
addField(ConfigureNodeFields.persist_items, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.persist_items.getFieldName(), persist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to deliver notifications to available users only.
|
||||
*
|
||||
* @return true if users must be available
|
||||
*/
|
||||
public boolean isPresenceBasedDelivery() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.presence_based_delivery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to deliver notifications to available users only.
|
||||
*
|
||||
* @param presenceBased true if user must be available, false otherwise
|
||||
*/
|
||||
public void setPresenceBasedDelivery(boolean presenceBased) {
|
||||
addField(ConfigureNodeFields.presence_based_delivery, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the publishing model for the node, which determines who may publish to it.
|
||||
*
|
||||
* @return The publishing model
|
||||
*/
|
||||
public PublishModel getPublishModel() {
|
||||
String value = getFieldValue(ConfigureNodeFields.publish_model);
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
else
|
||||
return PublishModel.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the publishing model for the node, which determines who may publish to it.
|
||||
*
|
||||
* @param publish The enum representing the possible options for the publishing model
|
||||
*/
|
||||
public void setPublishModel(PublishModel publish) {
|
||||
addField(ConfigureNodeFields.publish_model, FormField.Type.list_single);
|
||||
setAnswer(ConfigureNodeFields.publish_model.getFieldName(), getListSingle(publish.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* List of the multi user chat rooms that are specified as reply rooms.
|
||||
*
|
||||
* @return The reply room JID's
|
||||
*/
|
||||
public List<String> getReplyRoom() {
|
||||
return getFieldValues(ConfigureNodeFields.replyroom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the multi user chat rooms that are specified as reply rooms.
|
||||
*
|
||||
* @param replyRooms The multi user chat room to use as reply rooms
|
||||
*/
|
||||
public void setReplyRoom(List<String> replyRooms) {
|
||||
addField(ConfigureNodeFields.replyroom, FormField.Type.list_multi);
|
||||
setAnswer(ConfigureNodeFields.replyroom.getFieldName(), replyRooms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specific JID's for reply to.
|
||||
*
|
||||
* @return The JID's
|
||||
*/
|
||||
public List<String> getReplyTo() {
|
||||
return getFieldValues(ConfigureNodeFields.replyto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specific JID's for reply to.
|
||||
*
|
||||
* @param replyTos The JID's to reply to
|
||||
*/
|
||||
public void setReplyTo(List<String> replyTos) {
|
||||
addField(ConfigureNodeFields.replyto, FormField.Type.list_multi);
|
||||
setAnswer(ConfigureNodeFields.replyto.getFieldName(), replyTos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the roster groups that are allowed to subscribe and retrieve items.
|
||||
*
|
||||
* @return The roster groups
|
||||
*/
|
||||
public List<String> getRosterGroupsAllowed() {
|
||||
return getFieldValues(ConfigureNodeFields.roster_groups_allowed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the roster groups that are allowed to subscribe and retrieve items.
|
||||
*
|
||||
* @param groups The roster groups
|
||||
*/
|
||||
public void setRosterGroupsAllowed(List<String> groups) {
|
||||
addField(ConfigureNodeFields.roster_groups_allowed, FormField.Type.list_multi);
|
||||
setAnswer(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if subscriptions are allowed.
|
||||
*
|
||||
* @return true if subscriptions are allowed, false otherwise
|
||||
* @deprecated use {@link #isSubscribe()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
public boolean isSubscibe() {
|
||||
return isSubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if subscriptions are allowed.
|
||||
*
|
||||
* @return true if subscriptions are allowed, false otherwise
|
||||
*/
|
||||
public boolean isSubscribe() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.subscribe));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscriptions are allowed.
|
||||
*
|
||||
* @param subscribe true if they are, false otherwise
|
||||
*/
|
||||
public void setSubscribe(boolean subscribe) {
|
||||
addField(ConfigureNodeFields.subscribe, FormField.Type.bool);
|
||||
setAnswer(ConfigureNodeFields.subscribe.getFieldName(), subscribe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human readable node title.
|
||||
*
|
||||
* @return The node title
|
||||
*/
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getFieldValue(ConfigureNodeFields.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a human readable title for the node.
|
||||
*
|
||||
* @param title The node title
|
||||
*/
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
addField(ConfigureNodeFields.title, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.title.getFieldName(), title);
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of node data, usually specified by the namespace of the payload (if any).
|
||||
*
|
||||
* @return The type of node data
|
||||
*/
|
||||
public String getDataType() {
|
||||
return getFieldValue(ConfigureNodeFields.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of node data, usually specified by the namespace of the payload (if any).
|
||||
*
|
||||
* @param type The type of node data
|
||||
*/
|
||||
public void setDataType(String type) {
|
||||
addField(ConfigureNodeFields.type, FormField.Type.text_single);
|
||||
setAnswer(ConfigureNodeFields.type.getFieldName(), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder(getClass().getName() + " Content [");
|
||||
|
||||
for (FormField formField : getFields()) {
|
||||
result.append('(');
|
||||
result.append(formField.getVariable());
|
||||
result.append(':');
|
||||
|
||||
StringBuilder valuesBuilder = new StringBuilder();
|
||||
|
||||
for (CharSequence value : formField.getValues()) {
|
||||
if (valuesBuilder.length() > 0)
|
||||
result.append(',');
|
||||
valuesBuilder.append(value);
|
||||
}
|
||||
|
||||
if (valuesBuilder.length() == 0)
|
||||
valuesBuilder.append("NOT SET");
|
||||
result.append(valuesBuilder);
|
||||
result.append(')');
|
||||
}
|
||||
result.append(']');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String getFieldValue(ConfigureNodeFields field) {
|
||||
FormField formField = getField(field.getFieldName());
|
||||
|
||||
return formField.getFirstValue();
|
||||
}
|
||||
|
||||
private List<String> getFieldValues(ConfigureNodeFields field) {
|
||||
FormField formField = getField(field.getFieldName());
|
||||
|
||||
return formField.getValuesAsString();
|
||||
}
|
||||
|
||||
private void addField(ConfigureNodeFields nodeField, FormField.Type type) {
|
||||
String fieldName = nodeField.getFieldName();
|
||||
|
||||
if (getField(fieldName) == null) {
|
||||
FormField field = FormField.builder()
|
||||
.setVariable(fieldName)
|
||||
.setType(type)
|
||||
.build();
|
||||
addField(field);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> getListSingle(String value) {
|
||||
List<String> list = new ArrayList<>(1);
|
||||
list.add(value);
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,12 +18,13 @@ package org.jivesoftware.smackx.pubsub;
|
|||
|
||||
import java.net.URL;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.xdata.form.FilledForm;
|
||||
|
||||
/**
|
||||
* This enumeration represents all the fields of a node configuration form. This enumeration
|
||||
* is not required when using the {@link ConfigureForm} to configure nodes, but may be helpful
|
||||
* for generic UI's using only a {@link Form} for configuration.
|
||||
* for generic UI's using only a {@link FilledForm} for configuration.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
|
@ -176,20 +177,6 @@ public enum ConfigureNodeFields {
|
|||
*/
|
||||
publish_model,
|
||||
|
||||
/**
|
||||
* The specific multi-user chat rooms to specify for replyroom.
|
||||
*
|
||||
* <p><b>Value: List of JIDs as Strings</b></p>
|
||||
*/
|
||||
replyroom,
|
||||
|
||||
/**
|
||||
* The specific JID(s) to specify for replyto.
|
||||
*
|
||||
* <p><b>Value: List of JIDs as Strings</b></p>
|
||||
*/
|
||||
replyto,
|
||||
|
||||
/**
|
||||
* The roster group(s) allowed to subscribe and retrieve items.
|
||||
*
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
/**
|
||||
* Generic stanza extension which represents any PubSub form that is
|
||||
|
@ -27,7 +27,7 @@ import org.jivesoftware.smackx.xdata.Form;
|
|||
* @author Robin Collier
|
||||
*/
|
||||
public class FormNode extends NodeExtension {
|
||||
private final Form configForm;
|
||||
private final DataForm configForm;
|
||||
|
||||
/**
|
||||
* Create a {@link FormNode} which contains the specified form.
|
||||
|
@ -35,7 +35,7 @@ public class FormNode extends NodeExtension {
|
|||
* @param formType The type of form being sent
|
||||
* @param submitForm The form
|
||||
*/
|
||||
public FormNode(FormNodeType formType, Form submitForm) {
|
||||
public FormNode(FormNodeType formType, DataForm submitForm) {
|
||||
super(formType.getNodeElement());
|
||||
|
||||
if (submitForm == null)
|
||||
|
@ -51,7 +51,7 @@ public class FormNode extends NodeExtension {
|
|||
* @param nodeId The node the form is associated with
|
||||
* @param submitForm The form
|
||||
*/
|
||||
public FormNode(FormNodeType formType, String nodeId, Form submitForm) {
|
||||
public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) {
|
||||
super(formType.getNodeElement(), nodeId);
|
||||
|
||||
if (submitForm == null)
|
||||
|
@ -64,7 +64,7 @@ public class FormNode extends NodeExtension {
|
|||
*
|
||||
* @return The form
|
||||
*/
|
||||
public Form getForm() {
|
||||
public DataForm getForm() {
|
||||
return configForm;
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ public class FormNode extends NodeExtension {
|
|||
}
|
||||
else
|
||||
builder.append('>');
|
||||
builder.append(configForm.getDataFormToSend().toXML());
|
||||
builder.append(configForm.toXML());
|
||||
builder.append("</");
|
||||
builder.append(getElementName() + '>');
|
||||
return builder.toString();
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.jivesoftware.smackx.pubsub;
|
|||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
|
||||
|
||||
/**
|
||||
* These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)},
|
||||
* These are the options for the node configuration setting {@link FillableConfigureForm#setItemReply(ItemReply)},
|
||||
* which defines who should receive replies to items.
|
||||
*
|
||||
* @author Robin Collier
|
||||
|
|
|
@ -135,6 +135,7 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten
|
|||
*
|
||||
* @return List of {@link Item}, {@link RetractItem}, or null
|
||||
*/
|
||||
// TODO: Shouldn't this return List<Item>? Why is RetractItem not a subtype of item?
|
||||
public List<? extends NamedElement> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
|||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,6 +37,10 @@ import org.jivesoftware.smackx.delay.DelayInformationManager;
|
|||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace;
|
||||
import org.jivesoftware.smackx.pubsub.SubscriptionsExtension.SubscriptionsNamespace;
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableSubscribeForm;
|
||||
import org.jivesoftware.smackx.pubsub.form.SubscribeForm;
|
||||
import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener;
|
||||
import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
|
||||
import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener;
|
||||
|
@ -45,7 +49,7 @@ import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
|
|||
import org.jivesoftware.smackx.pubsub.util.NodeUtils;
|
||||
import org.jivesoftware.smackx.shim.packet.Header;
|
||||
import org.jivesoftware.smackx.shim.packet.HeadersExtension;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
|
@ -81,7 +85,7 @@ public abstract class Node {
|
|||
}
|
||||
/**
|
||||
* Returns a configuration form, from which you can create an answer form to be submitted
|
||||
* via the {@link #sendConfigurationForm(Form)}.
|
||||
* via the {@link #sendConfigurationForm(FillableConfigureForm)}.
|
||||
*
|
||||
* @return the configuration form
|
||||
* @throws XMPPErrorException if there was an XMPP error returned.
|
||||
|
@ -97,17 +101,17 @@ public abstract class Node {
|
|||
}
|
||||
|
||||
/**
|
||||
* Update the configuration with the contents of the new {@link Form}.
|
||||
* Update the configuration with the contents of the new {@link FillableConfigureForm}.
|
||||
*
|
||||
* @param submitForm TODO javadoc me please
|
||||
* @param configureForm the filled node configuration form with the nodes new configuration.
|
||||
* @throws XMPPErrorException if there was an XMPP error returned.
|
||||
* @throws NoResponseException if there was no response from the remote entity.
|
||||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public void sendConfigurationForm(Form submitForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public void sendConfigurationForm(FillableConfigureForm configureForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER,
|
||||
getId(), submitForm));
|
||||
getId(), configureForm.getDataFormToSubmit()));
|
||||
pubSubManager.getConnection().createStanzaCollectorAndSend(packet).nextResultOrThrow();
|
||||
}
|
||||
|
||||
|
@ -454,9 +458,10 @@ public abstract class Node {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public Subscription subscribe(Jid jid, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public Subscription subscribe(Jid jid, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
DataForm submitForm = subForm.getDataFormToSubmit();
|
||||
PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
|
||||
request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
|
||||
request.addExtension(new FormNode(FormNodeType.OPTIONS, submitForm));
|
||||
PubSub reply = sendPubsubPacket(request);
|
||||
return reply.getExtension(PubSubElementType.SUBSCRIPTION);
|
||||
}
|
||||
|
@ -483,11 +488,11 @@ public abstract class Node {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws IllegalArgumentException if the provided string is not a valid JID.
|
||||
* @deprecated use {@link #subscribe(Jid, SubscribeForm)} instead.
|
||||
* @deprecated use {@link #subscribe(Jid, FillableSubscribeForm)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
public Subscription subscribe(String jidString, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public Subscription subscribe(String jidString, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
Jid jid;
|
||||
try {
|
||||
jid = JidCreate.from(jidString);
|
||||
|
@ -529,7 +534,7 @@ public abstract class Node {
|
|||
|
||||
/**
|
||||
* Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted
|
||||
* via the {@link #sendConfigurationForm(Form)}.
|
||||
* via the {@link #sendConfigurationForm(FillableConfigureForm)}.
|
||||
*
|
||||
* @param jid TODO javadoc me please
|
||||
*
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
|
||||
|
||||
/**
|
||||
* Specify the delivery style for event notifications. Denotes possible values
|
||||
* for {@link ConfigureForm#setNotificationType(NotificationType)}.
|
||||
* for {@link FillableConfigureForm#setNotificationType(NotificationType)}.
|
||||
*
|
||||
* @author Timothy Pitt
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
|||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.SubscribeForm;
|
||||
|
||||
/**
|
||||
* Defines the possible valid presence states for node subscription via
|
||||
* {@link SubscribeForm#getShowValues()}.
|
||||
|
|
|
@ -46,11 +46,12 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
|||
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
|
||||
import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
|
||||
import org.jivesoftware.smackx.pubsub.util.NodeUtils;
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
|
@ -69,6 +70,8 @@ import org.jxmpp.stringprep.XmppStringprepException;
|
|||
*/
|
||||
public final class PubSubManager extends Manager {
|
||||
|
||||
public static final String PLUS_NOTIFY = "+notify";
|
||||
|
||||
public static final String AUTO_CREATE_FEATURE = "http://jabber.org/protocol/pubsub#auto-create";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PubSubManager.class.getName());
|
||||
|
@ -253,16 +256,18 @@ public final class PubSubManager extends Manager {
|
|||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
public Node createNode(String nodeId, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
public Node createNode(String nodeId, FillableConfigureForm config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
PubSub request = PubSub.createPubsubPacket(pubSubService, Type.set, new NodeExtension(PubSubElementType.CREATE, nodeId));
|
||||
boolean isLeafNode = true;
|
||||
|
||||
if (config != null) {
|
||||
request.addExtension(new FormNode(FormNodeType.CONFIGURE, config));
|
||||
FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName());
|
||||
|
||||
if (nodeTypeField != null)
|
||||
isLeafNode = nodeTypeField.getValues().get(0).toString().equals(NodeType.leaf.toString());
|
||||
DataForm submitForm = config.getDataFormToSubmit();
|
||||
request.addExtension(new FormNode(FormNodeType.CONFIGURE, submitForm));
|
||||
NodeType nodeType = config.getNodeType();
|
||||
// Note that some implementations do to have the pubsub#node_type field in their defauilt configuration,
|
||||
// which I believe to be a bug. However, since PubSub specifies the default node type to be 'leaf' we assume
|
||||
// leaf if the field does not exist.
|
||||
isLeafNode = nodeType == null || nodeType == NodeType.leaf;
|
||||
}
|
||||
|
||||
// Errors will cause exceptions in getReply, so it only returns
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
|
||||
|
||||
/**
|
||||
* Determines who may publish to a node. Denotes possible values
|
||||
* for {@link ConfigureForm#setPublishModel(PublishModel)}.
|
||||
* for {@link FillableConfigureForm#setPublishModel(PublishModel)}.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.pubsub;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UnknownFormatConversionException;
|
||||
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.Form;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
|
||||
/**
|
||||
* A decorator for a {@link Form} to easily enable reading and updating
|
||||
* of subscription options. All operations read or update the underlying {@link DataForm}.
|
||||
*
|
||||
* <p>Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not
|
||||
* exist, all <b>SubscribeForm.setXXX</b> methods will create the field in the wrapped form
|
||||
* if it does not already exist.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
public class SubscribeForm extends Form {
|
||||
public SubscribeForm(DataForm configDataForm) {
|
||||
super(configDataForm);
|
||||
}
|
||||
|
||||
public SubscribeForm(Form subscribeOptionsForm) {
|
||||
super(subscribeOptionsForm.getDataFormToSend());
|
||||
}
|
||||
|
||||
public SubscribeForm(DataForm.Type formType) {
|
||||
super(formType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an entity wants to receive notifications.
|
||||
*
|
||||
* @return true if want to receive, false otherwise
|
||||
*/
|
||||
public boolean isDeliverOn() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.deliver));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether an entity wants to receive notifications.
|
||||
*
|
||||
* @param deliverNotifications TODO javadoc me please
|
||||
*/
|
||||
public void setDeliverOn(boolean deliverNotifications) {
|
||||
addField(SubscribeOptionFields.deliver, FormField.Type.bool);
|
||||
setAnswer(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if notifications should be delivered as aggregations or not.
|
||||
*
|
||||
* @return true to aggregate, false otherwise
|
||||
*/
|
||||
public boolean isDigestOn() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.digest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether notifications should be delivered as aggregations or not.
|
||||
*
|
||||
* @param digestOn true to aggregate, false otherwise
|
||||
*/
|
||||
public void setDigestOn(boolean digestOn) {
|
||||
addField(SubscribeOptionFields.deliver, FormField.Type.bool);
|
||||
setAnswer(SubscribeOptionFields.deliver.getFieldName(), digestOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum number of milliseconds between sending notification digests.
|
||||
*
|
||||
* @return The frequency in milliseconds
|
||||
*/
|
||||
public int getDigestFrequency() {
|
||||
return Integer.parseInt(getFieldValue(SubscribeOptionFields.digest_frequency));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of milliseconds between sending notification digests.
|
||||
*
|
||||
* @param frequency The frequency in milliseconds
|
||||
*/
|
||||
public void setDigestFrequency(int frequency) {
|
||||
addField(SubscribeOptionFields.digest_frequency, FormField.Type.text_single);
|
||||
setAnswer(SubscribeOptionFields.digest_frequency.getFieldName(), frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time at which the leased subscription will expire, or has expired.
|
||||
*
|
||||
* @return The expiry date
|
||||
*/
|
||||
public Date getExpiry() {
|
||||
String dateTime = getFieldValue(SubscribeOptionFields.expire);
|
||||
try {
|
||||
return XmppDateTime.parseDate(dateTime);
|
||||
}
|
||||
catch (ParseException e) {
|
||||
UnknownFormatConversionException exc = new UnknownFormatConversionException(dateTime);
|
||||
exc.initCause(e);
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time at which the leased subscription will expire, or has expired.
|
||||
*
|
||||
* @param expire The expiry date
|
||||
*/
|
||||
public void setExpiry(Date expire) {
|
||||
addField(SubscribeOptionFields.expire, FormField.Type.text_single);
|
||||
setAnswer(SubscribeOptionFields.expire.getFieldName(), XmppDateTime.formatXEP0082Date(expire));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the entity wants to receive an XMPP message body in
|
||||
* addition to the payload format.
|
||||
*
|
||||
* @return true to receive the message body, false otherwise
|
||||
*/
|
||||
public boolean isIncludeBody() {
|
||||
return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.include_body));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the entity wants to receive an XMPP message body in
|
||||
* addition to the payload format.
|
||||
*
|
||||
* @param include true to receive the message body, false otherwise
|
||||
*/
|
||||
public void setIncludeBody(boolean include) {
|
||||
addField(SubscribeOptionFields.include_body, FormField.Type.bool);
|
||||
setAnswer(SubscribeOptionFields.include_body.getFieldName(), include);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link PresenceState} for which an entity wants to receive
|
||||
* notifications.
|
||||
*
|
||||
* @return the list of states
|
||||
*/
|
||||
public List<PresenceState> getShowValues() {
|
||||
ArrayList<PresenceState> result = new ArrayList<>(5);
|
||||
|
||||
for (String state : getFieldValues(SubscribeOptionFields.show_values)) {
|
||||
result.add(PresenceState.valueOf(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of {@link PresenceState} for which an entity wants
|
||||
* to receive notifications.
|
||||
*
|
||||
* @param stateValues The list of states
|
||||
*/
|
||||
public void setShowValues(Collection<PresenceState> stateValues) {
|
||||
ArrayList<String> values = new ArrayList<>(stateValues.size());
|
||||
|
||||
for (PresenceState state : stateValues) {
|
||||
values.add(state.toString());
|
||||
}
|
||||
addField(SubscribeOptionFields.show_values, FormField.Type.list_multi);
|
||||
setAnswer(SubscribeOptionFields.show_values.getFieldName(), values);
|
||||
}
|
||||
|
||||
private String getFieldValue(SubscribeOptionFields field) {
|
||||
FormField formField = getField(field.getFieldName());
|
||||
|
||||
return formField.getFirstValue();
|
||||
}
|
||||
|
||||
private List<String> getFieldValues(SubscribeOptionFields field) {
|
||||
FormField formField = getField(field.getFieldName());
|
||||
|
||||
return formField.getValuesAsString();
|
||||
}
|
||||
|
||||
private void addField(SubscribeOptionFields nodeField, FormField.Type type) {
|
||||
String fieldName = nodeField.getFieldName();
|
||||
|
||||
if (getField(fieldName) == null) {
|
||||
FormField.Builder field = FormField.builder(fieldName);
|
||||
field.setType(type);
|
||||
addField(field.build());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.filter;
|
||||
|
||||
import org.jivesoftware.smack.filter.ExtensionElementFilter;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.EventElementType;
|
||||
|
||||
public final class EventItemsExtensionFilter extends ExtensionElementFilter<EventElement> {
|
||||
|
||||
public static final EventItemsExtensionFilter INSTANCE = new EventItemsExtensionFilter();
|
||||
|
||||
private EventItemsExtensionFilter() {
|
||||
super(EventElement.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(EventElement eventElement) {
|
||||
EventElementType eventElementType = eventElement.getEventType();
|
||||
return eventElementType == EventElementType.items;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.form.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class ConfigureForm extends Form implements ConfigureFormReader {
|
||||
|
||||
public ConfigureForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
ensureFormType(dataForm, FORM_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FillableConfigureForm getFillableForm() {
|
||||
return new FillableConfigureForm(getDataForm());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors, 2020 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.pubsub.form;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.AccessModel;
|
||||
import org.jivesoftware.smackx.pubsub.ChildrenAssociationPolicy;
|
||||
import org.jivesoftware.smackx.pubsub.ConfigureNodeFields;
|
||||
import org.jivesoftware.smackx.pubsub.ItemReply;
|
||||
import org.jivesoftware.smackx.pubsub.NodeType;
|
||||
import org.jivesoftware.smackx.pubsub.NotificationType;
|
||||
import org.jivesoftware.smackx.pubsub.PublishModel;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.JidMultiFormField;
|
||||
import org.jivesoftware.smackx.xdata.form.FormReader;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
public interface ConfigureFormReader extends FormReader {
|
||||
|
||||
String FORM_TYPE = PubSub.NAMESPACE + "#node_config";
|
||||
|
||||
/**
|
||||
* Get the currently configured {@link AccessModel}, null if it is not set.
|
||||
*
|
||||
* @return The current {@link AccessModel}
|
||||
*/
|
||||
default AccessModel getAccessModel() {
|
||||
String value = readFirstValue(ConfigureNodeFields.access_model.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return AccessModel.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of an XSL transformation which can be applied to payloads in order to
|
||||
* generate an appropriate message body element.
|
||||
*
|
||||
* @return URL to an XSL
|
||||
*/
|
||||
default String getBodyXSLT() {
|
||||
return readFirstValue(ConfigureNodeFields.body_xslt.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* The id's of the child nodes associated with a collection node (both leaf and collection).
|
||||
*
|
||||
* @return list of child nodes.
|
||||
*/
|
||||
default List<String> getChildren() {
|
||||
return readStringValues(ConfigureNodeFields.children.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the policy that determines who may associate children with the node.
|
||||
*
|
||||
* @return The current policy
|
||||
*/
|
||||
default ChildrenAssociationPolicy getChildrenAssociationPolicy() {
|
||||
String value = readFirstValue(ConfigureNodeFields.children_association_policy.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return ChildrenAssociationPolicy.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of JID's that are on the whitelist that determines who can associate child nodes
|
||||
* with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
|
||||
* {@link ChildrenAssociationPolicy#whitelist}.
|
||||
*
|
||||
* @return List of the whitelist
|
||||
*/
|
||||
default List<Jid> getChildrenAssociationWhitelist() {
|
||||
FormField formField = read(ConfigureNodeFields.children_association_whitelist.getFieldName());
|
||||
if (formField == null) {
|
||||
Collections.emptyList();
|
||||
}
|
||||
JidMultiFormField jidMultiFormField = formField.ifPossibleAs(JidMultiFormField.class);
|
||||
return jidMultiFormField.getValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of child nodes that can be associated with the collection node.
|
||||
*
|
||||
* @return The maximum number of child nodes
|
||||
*/
|
||||
default Integer getChildrenMax() {
|
||||
return readInteger(ConfigureNodeFields.children_max.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection node which the node is affiliated with.
|
||||
*
|
||||
* @return The collection node id
|
||||
*/
|
||||
default List<? extends CharSequence> getCollection() {
|
||||
return readValues(ConfigureNodeFields.collection.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of an XSL transformation which can be applied to the payload
|
||||
* format in order to generate a valid Data Forms result that the client could
|
||||
* display using a generic Data Forms rendering engine.
|
||||
*
|
||||
* @return The URL of an XSL transformation
|
||||
*/
|
||||
default String getDataformXSLT() {
|
||||
return readFirstValue(ConfigureNodeFields.dataform_xslt.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the node deliver payloads with event notifications.
|
||||
*
|
||||
* @return true if it does, false otherwise
|
||||
*/
|
||||
default Boolean isDeliverPayloads() {
|
||||
return readBoolean(ConfigureNodeFields.deliver_payloads.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines who should get replies to items.
|
||||
*
|
||||
* @return Who should get the reply
|
||||
*/
|
||||
default ItemReply getItemReply() {
|
||||
String value = readFirstValue(ConfigureNodeFields.itemreply.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return ItemReply.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is
|
||||
* true.
|
||||
*
|
||||
* @return The maximum number of items to persist
|
||||
*/
|
||||
default Integer getMaxItems() {
|
||||
return readInteger(ConfigureNodeFields.max_items.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum payload size in bytes.
|
||||
*
|
||||
* @return The maximum payload size
|
||||
*/
|
||||
default Integer getMaxPayloadSize() {
|
||||
return readInteger(ConfigureNodeFields.max_payload_size.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node type.
|
||||
*
|
||||
* @return The node type
|
||||
*/
|
||||
default NodeType getNodeType() {
|
||||
String value = readFirstValue(ConfigureNodeFields.node_type.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return NodeType.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if subscribers should be notified when the configuration changes.
|
||||
*
|
||||
* @return true if they should be notified, false otherwise
|
||||
*/
|
||||
default Boolean isNotifyConfig() {
|
||||
return readBoolean(ConfigureNodeFields.notify_config.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether subscribers should be notified when the node is deleted.
|
||||
*
|
||||
* @return true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
default Boolean isNotifyDelete() {
|
||||
return readBoolean(ConfigureNodeFields.notify_delete.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether subscribers should be notified when items are deleted
|
||||
* from the node.
|
||||
*
|
||||
* @return true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
default Boolean isNotifyRetract() {
|
||||
return readBoolean(ConfigureNodeFields.notify_retract.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the type of notifications which are sent.
|
||||
*
|
||||
* @return NotificationType for the node configuration
|
||||
* @since 4.3
|
||||
*/
|
||||
default NotificationType getNotificationType() {
|
||||
String value = readFirstValue(ConfigureNodeFields.notification_type.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return NotificationType.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether items should be persisted in the node.
|
||||
*
|
||||
* @return true if items are persisted
|
||||
*/
|
||||
default boolean isPersistItems() {
|
||||
return readBoolean(ConfigureNodeFields.persist_items.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to deliver notifications to available users only.
|
||||
*
|
||||
* @return true if users must be available
|
||||
*/
|
||||
default boolean isPresenceBasedDelivery() {
|
||||
return readBoolean(ConfigureNodeFields.presence_based_delivery.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the publishing model for the node, which determines who may publish to it.
|
||||
*
|
||||
* @return The publishing model
|
||||
*/
|
||||
default PublishModel getPublishModel() {
|
||||
String value = readFirstValue(ConfigureNodeFields.publish_model.getFieldName());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return PublishModel.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the roster groups that are allowed to subscribe and retrieve items.
|
||||
*
|
||||
* @return The roster groups
|
||||
*/
|
||||
default List<String> getRosterGroupsAllowed() {
|
||||
return readStringValues(ConfigureNodeFields.roster_groups_allowed.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if subscriptions are allowed.
|
||||
*
|
||||
* @return true if subscriptions are allowed, false otherwise
|
||||
*/
|
||||
default boolean isSubscribe() {
|
||||
return readBoolean(ConfigureNodeFields.subscribe.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human readable node title.
|
||||
*
|
||||
* @return The node title
|
||||
*/
|
||||
default String getTitle() {
|
||||
return readFirstValue(ConfigureNodeFields.title.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of node data, usually specified by the namespace of the payload (if any).
|
||||
*
|
||||
* @return The type of node data
|
||||
*/
|
||||
default String getDataType() {
|
||||
return readFirstValue(ConfigureNodeFields.type.getFieldName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors, 2020 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.pubsub.form;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.AccessModel;
|
||||
import org.jivesoftware.smackx.pubsub.ChildrenAssociationPolicy;
|
||||
import org.jivesoftware.smackx.pubsub.ConfigureNodeFields;
|
||||
import org.jivesoftware.smackx.pubsub.ItemReply;
|
||||
import org.jivesoftware.smackx.pubsub.NodeType;
|
||||
import org.jivesoftware.smackx.pubsub.NotificationType;
|
||||
import org.jivesoftware.smackx.pubsub.PublishModel;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
public class FillableConfigureForm extends FillableForm implements ConfigureFormReader {
|
||||
|
||||
public FillableConfigureForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of access model.
|
||||
*
|
||||
* @param accessModel TODO javadoc me please
|
||||
*/
|
||||
public void setAccessModel(AccessModel accessModel) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.access_model.getFieldName())
|
||||
.setValue(accessModel)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL of an XSL transformation which can be applied to payloads in order to
|
||||
* generate an appropriate message body element.
|
||||
*
|
||||
* @param bodyXslt The URL of an XSL
|
||||
*/
|
||||
public void setBodyXSLT(String bodyXslt) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.body_xslt.getFieldName())
|
||||
.setValue(bodyXslt)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of child node ids that are associated with a collection node.
|
||||
*
|
||||
* @param children TODO javadoc me please
|
||||
*/
|
||||
public void setChildren(List<String> children) {
|
||||
FormField formField = FormField.textMultiBuilder(ConfigureNodeFields.children.getFieldName())
|
||||
.addValues(children)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the policy that determines who may associate children with the node.
|
||||
*
|
||||
* @param policy The policy being set
|
||||
*/
|
||||
public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.children_association_policy.getFieldName())
|
||||
.setValue(policy)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JID's in the whitelist of users that can associate child nodes with the collection
|
||||
* node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
|
||||
* {@link ChildrenAssociationPolicy#whitelist}.
|
||||
*
|
||||
* @param whitelist The list of JID's
|
||||
*/
|
||||
public void setChildrenAssociationWhitelist(List<? extends Jid> whitelist) {
|
||||
FormField formField = FormField.jidMultiBuilder(ConfigureNodeFields.children_association_whitelist.getFieldName())
|
||||
.addValues(whitelist)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of child nodes that can be associated with a collection node.
|
||||
*
|
||||
* @param max The maximum number of child nodes.
|
||||
*/
|
||||
public void setChildrenMax(int max) {
|
||||
FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.children_max.getFieldName())
|
||||
.setValue(max)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection node which the node is affiliated with.
|
||||
*
|
||||
* @param collection The node id of the collection node
|
||||
*/
|
||||
public void setCollection(String collection) {
|
||||
setCollections(Collections.singletonList(collection));
|
||||
}
|
||||
|
||||
public void setCollections(Collection<String> collections) {
|
||||
FormField formField = FormField.textMultiBuilder(ConfigureNodeFields.collection.getFieldName())
|
||||
.addValues(collections)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of an XSL transformation which can be applied to the payload
|
||||
* format in order to generate a valid Data Forms result that the client could
|
||||
* display using a generic Data Forms rendering engine.
|
||||
*
|
||||
* @param url The URL of an XSL transformation
|
||||
*/
|
||||
public void setDataformXSLT(URL url) {
|
||||
FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.dataform_xslt.getFieldName())
|
||||
.setValue(url)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the node will deliver payloads with event notifications.
|
||||
*
|
||||
* @param deliver true if the payload will be delivered, false otherwise
|
||||
*/
|
||||
public void setDeliverPayloads(boolean deliver) {
|
||||
writeBoolean(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who should get the replies to items.
|
||||
*
|
||||
* @param reply Defines who should get the reply
|
||||
*/
|
||||
public void setItemReply(ItemReply reply) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.itemreply.getFieldName())
|
||||
.setValue(reply)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is
|
||||
* true.
|
||||
*
|
||||
* @param max The maximum number of items to persist
|
||||
*/
|
||||
public void setMaxItems(int max) {
|
||||
FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.max_items.getFieldName())
|
||||
.setValue(max)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum payload size in bytes.
|
||||
*
|
||||
* @param max The maximum payload size
|
||||
*/
|
||||
public void setMaxPayloadSize(int max) {
|
||||
FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.max_payload_size.getFieldName())
|
||||
.setValue(max)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node type.
|
||||
*
|
||||
* @param type The node type
|
||||
*/
|
||||
public void setNodeType(NodeType type) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.node_type.getFieldName())
|
||||
.setValue(type)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when the configuration changes.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyConfig(boolean notify) {
|
||||
writeBoolean(ConfigureNodeFields.notify_config.getFieldName(), notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when the node is deleted.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyDelete(boolean notify) {
|
||||
writeBoolean(ConfigureNodeFields.notify_delete.getFieldName(), notify);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether subscribers should be notified when items are deleted
|
||||
* from the node.
|
||||
*
|
||||
* @param notify true if subscribers should be notified, false otherwise
|
||||
*/
|
||||
public void setNotifyRetract(boolean notify) {
|
||||
writeBoolean(ConfigureNodeFields.notify_retract.getFieldName(), notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the NotificationType for the node.
|
||||
*
|
||||
* @param notificationType The enum representing the possible options
|
||||
* @since 4.3
|
||||
*/
|
||||
public void setNotificationType(NotificationType notificationType) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.notification_type.getFieldName())
|
||||
.setValue(notificationType)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether items should be persisted in the node.
|
||||
*
|
||||
* @param persist true if items should be persisted, false otherwise
|
||||
*/
|
||||
public void setPersistentItems(boolean persist) {
|
||||
writeBoolean(ConfigureNodeFields.persist_items.getFieldName(), persist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to deliver notifications to available users only.
|
||||
*
|
||||
* @param presenceBased true if user must be available, false otherwise
|
||||
*/
|
||||
public void setPresenceBasedDelivery(boolean presenceBased) {
|
||||
writeBoolean(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the publishing model for the node, which determines who may publish to it.
|
||||
*
|
||||
* @param publish The enum representing the possible options for the publishing model
|
||||
*/
|
||||
public void setPublishModel(PublishModel publish) {
|
||||
FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.publish_model.getFieldName())
|
||||
.setValue(publish)
|
||||
.build();
|
||||
write(formField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the roster groups that are allowed to subscribe and retrieve items.
|
||||
*
|
||||
* @param groups The roster groups
|
||||
*/
|
||||
public void setRosterGroupsAllowed(List<? extends CharSequence> groups) {
|
||||
writeListMulti(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether subscriptions are allowed.
|
||||
*
|
||||
* @param subscribe true if they are, false otherwise
|
||||
*/
|
||||
public void setSubscribe(boolean subscribe) {
|
||||
writeBoolean(ConfigureNodeFields.subscribe.getFieldName(), subscribe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a human readable title for the node.
|
||||
*
|
||||
* @param title The node title
|
||||
*/
|
||||
public void setTitle(CharSequence title) {
|
||||
writeTextSingle(ConfigureNodeFields.title.getFieldName(), title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of node data, usually specified by the namespace of the payload (if any).
|
||||
*
|
||||
* @param type The type of node data
|
||||
*/
|
||||
public void setDataType(CharSequence type) {
|
||||
writeTextSingle(ConfigureNodeFields.type.getFieldName(), type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.PresenceState;
|
||||
import org.jivesoftware.smackx.pubsub.SubscribeOptionFields;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.ListMultiFormField;
|
||||
import org.jivesoftware.smackx.xdata.form.FillableForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class FillableSubscribeForm extends FillableForm {
|
||||
|
||||
public FillableSubscribeForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether an entity wants to receive notifications.
|
||||
*
|
||||
* @param deliverNotifications TODO javadoc me please
|
||||
*/
|
||||
public void setDeliverOn(boolean deliverNotifications) {
|
||||
writeBoolean(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether notifications should be delivered as aggregations or not.
|
||||
*
|
||||
* @param digestOn true to aggregate, false otherwise
|
||||
*/
|
||||
public void setDigestOn(boolean digestOn) {
|
||||
writeBoolean(SubscribeOptionFields.digest.getFieldName(), digestOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of milliseconds between sending notification digests.
|
||||
*
|
||||
* @param frequency The frequency in milliseconds
|
||||
*/
|
||||
public void setDigestFrequency(int frequency) {
|
||||
write(SubscribeOptionFields.digest_frequency.getFieldName(), frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time at which the leased subscription will expire, or has expired.
|
||||
*
|
||||
* @param expire The expiry date
|
||||
*/
|
||||
public void setExpiry(Date expire) {
|
||||
write(SubscribeOptionFields.expire.getFieldName(), expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the entity wants to receive an XMPP message body in
|
||||
* addition to the payload format.
|
||||
*
|
||||
* @param include true to receive the message body, false otherwise
|
||||
*/
|
||||
public void setIncludeBody(boolean include) {
|
||||
writeBoolean(SubscribeOptionFields.include_body.getFieldName(), include);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of {@link PresenceState} for which an entity wants
|
||||
* to receive notifications.
|
||||
*
|
||||
* @param stateValues The list of states
|
||||
*/
|
||||
public void setShowValues(Collection<PresenceState> stateValues) {
|
||||
ListMultiFormField.Builder builder = FormField.listMultiBuilder(SubscribeOptionFields.show_values.getFieldName());
|
||||
for (PresenceState state : stateValues) {
|
||||
builder.addValue(state.toString());
|
||||
}
|
||||
|
||||
write(builder.build());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.form.FilledForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class FilledConfigureForm extends FilledForm implements ConfigureFormReader {
|
||||
|
||||
public FilledConfigureForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
ensureFormType(dataForm, FORM_TYPE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.form.FilledForm;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class FilledSubscribeForm extends FilledForm implements SubscribeFormReader {
|
||||
|
||||
public FilledSubscribeForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
ensureFormType(dataForm, FORM_TYPE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import org.jivesoftware.smackx.xdata.form.Form;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
public class SubscribeForm extends Form implements SubscribeFormReader {
|
||||
|
||||
public SubscribeForm(DataForm dataForm) {
|
||||
super(dataForm);
|
||||
ensureFormType(dataForm, FORM_TYPE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.pubsub.form;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smackx.pubsub.PresenceState;
|
||||
import org.jivesoftware.smackx.pubsub.SubscribeOptionFields;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
import org.jivesoftware.smackx.xdata.form.FormReader;
|
||||
|
||||
public interface SubscribeFormReader extends FormReader {
|
||||
|
||||
String FORM_TYPE = PubSub.NAMESPACE + "#subscribe_options";
|
||||
|
||||
/**
|
||||
* Determines if an entity wants to receive notifications.
|
||||
*
|
||||
* @return true if want to receive, false otherwise
|
||||
*/
|
||||
default boolean isDeliverOn() {
|
||||
return readBoolean(SubscribeOptionFields.deliver.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if notifications should be delivered as aggregations or not.
|
||||
*
|
||||
* @return true to aggregate, false otherwise
|
||||
*/
|
||||
default Boolean isDigestOn() {
|
||||
return readBoolean(SubscribeOptionFields.digest.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum number of milliseconds between sending notification digests.
|
||||
*
|
||||
* @return The frequency in milliseconds
|
||||
*/
|
||||
default Integer getDigestFrequency() {
|
||||
return readInteger(SubscribeOptionFields.digest_frequency.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time at which the leased subscription will expire, or has expired.
|
||||
*
|
||||
* @return The expiry date
|
||||
* @throws ParseException in case the date could not be parsed.
|
||||
*/
|
||||
default Date getExpiry() throws ParseException {
|
||||
return readDate(SubscribeOptionFields.expire.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the entity wants to receive an XMPP message body in
|
||||
* addition to the payload format.
|
||||
*
|
||||
* @return true to receive the message body, false otherwise
|
||||
*/
|
||||
default Boolean isIncludeBody() {
|
||||
return readBoolean(SubscribeOptionFields.include_body.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link PresenceState} for which an entity wants to receive
|
||||
* notifications.
|
||||
*
|
||||
* @return the list of states
|
||||
*/
|
||||
default List<PresenceState> getShowValues() {
|
||||
List<String> values = readStringValues(SubscribeOptionFields.show_values.getFieldName());
|
||||
List<PresenceState> result = new ArrayList<>(values.size());
|
||||
|
||||
for (String state : values) {
|
||||
result.add(PresenceState.valueOf(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smack's implementation of Data Forms (XEP-0004) for PubSub.
|
||||
*/
|
||||
package org.jivesoftware.smackx.pubsub.form;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue