1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-02-18 14:49:23 +01:00

Compare commits

...

57 commits

Author SHA1 Message Date
Florian Schmaus
498dde2d86 Merge branch 'master' of github.com:igniterealtime/Smack 2020-05-13 20:20:01 +02:00
Florian Schmaus
77e26fc575 Re-work data form API
Apply builder pattern to form fields and replace getVariable() with
getFieldName(). Refer to the field name as "field name" instead of
"variable" everyone, just as XEP-0004 does.

Improve the high-level form API: introduce FilledForm and FillableForm
which perform stronger validation and consistency checks.

Also add FormFieldRegistry to enable processing of 'submit' forms
where the form field types are omitted.

Smack also now does omit the form field type declaration on 'submit'
type forms, as it is allowed by XEP-0004.
2020-05-13 20:14:41 +02:00
Florian Schmaus
25a5261dc0
Merge pull request #389 from guusdk/pep-notification-message-type
pep: notification type can be normal or headline
2020-05-12 20:07:43 +02:00
Guus der Kinderen
577c59484b pep: notification type can be normal or headline 2020-05-12 19:57:56 +02:00
Florian Schmaus
3270c113c5 [filetransfer] Remove FaultTolerantNegotiator
The FaultTolerantNegotiator is the reason why Smack replies in a
non-standard way to file transfer requests: Smack puts two values in
the stream-method field, while the field is a list-single field,
i.e. a field which only allows one value.

Even if what Smack does is probably better, as it allows for a
fallback in case the bytestream transport fails, it is not standard
compliant. And, Jingle provide a proper fallback specification for
file transfers.

Fixes SMACK-561.
2020-05-11 22:12:22 +02:00
Florian Schmaus
2c39dff653 pubsub: remove 'replyto' and 'replyroom' configure settings
Those configurations where removed with version 1.13 (2010-07-12) of
XEP-0060.

This change is part of the effort to upgrade Smack's PubSub
implementation (SMACK-364).
2020-05-04 10:55:36 +02:00
Florian Schmaus
f61ecb65e7 Make java(c|doc) use --release when available
In order to truely stay Java 8 compatible, declaring a source and
target compatiblity is not sufficient. Source compatiblity means that
the input, i.e. the code written in Java is compatible with that
particular version of the Java Language Specification (JLS). And
target compatibitliy means that the produced Java bytecode is
compatible with that particular version of the Java Virtual Machine
Specificiation (JVMS).

But there is actually a third dimension: the runtime
library (rt.jar). If signatures of methods change over java releases
within the runtime library, then the produced bytecode, may contain
calls to methods that do not exist with that exact same signature in
older java versions.

For example the family of Buffer subclasses changed the return value
of certain functions, for example flip() to not return Buffer, but the
concrete type of the current instance, e.g. CharBuffer.

If we compile now with a newer JDK, where the return type is
CharBuffer and not Buffer, then executing on an older JDK, where the
return type is Buffer, then we get java.lang.NoSuchMethodError(s)
thrown at us.

Fixes SMACK-651.
2020-04-24 10:15:13 +02:00
Florian Schmaus
e79710840b caps: handle multiple data forms of extened service discovery information 2020-04-18 22:56:24 +02:00
Florian Schmaus
5e921e6393 core: add javadoc for StanzaView.getExtensions(Class) 2020-04-18 22:56:10 +02:00
Florian Schmaus
dc443bccd4 disco: allow multiple data forms for extended discovery information
Previously Smack only supported a single data form as extended
discovery information.
2020-04-18 19:04:21 +02:00
Florian Schmaus
cdc5396f6c core: improve signature of Stanza.addExtensions() 2020-04-18 19:03:43 +02:00
Florian Schmaus
661b2743d9 mam: use new DataForm API in MamQueryIQ
Use the new API introduced with e58e6fa75 ("xdata: add more helper
methods to DataForm") in MamQueryIQ.
2020-04-18 19:03:00 +02:00
Florian Schmaus
e58e6fa75d xdata: add more helper methods to DataForm 2020-04-18 19:02:45 +02:00
Florian Schmaus
8e1003723e Specify correct and full version in @since javadoc tag
The next release of Smack will be 4.4.0.
2020-04-18 19:02:30 +02:00
Florian Schmaus
d0347d1e00 muc: add removed() callback to UserStatusListener 2020-04-17 22:26:28 +02:00
Jesus
c07f41c119 debug: show active tab on Smack debugger startup
Enhanced Debugger Window showed Smack Info tab, now shows active
connection. Fixes SMACK-367.
2020-04-17 21:03:05 +02:00
Florian Schmaus
5c5dfedce2
Merge pull request #383 from adiaholic/docFix
Correct erroneous documentation
2020-04-17 21:02:28 +02:00
Florian Schmaus
72b9f79692
Merge pull request #385 from adiaholic/bugFix
Remove unexpected `MucNotJoinedException` from `MultiUserChat.leave()`
2020-04-17 21:02:14 +02:00
adiaholic
22cff274bb Remove unexpected MucNotJoinedException from MultiUserChat.leave()
Occupant information should be reset after `leavePresence`
and `reflectedLeavePresenceFilter` are built.
2020-04-17 20:10:45 +05:30
Florian Schmaus
c3c14cfdb9 gradle: remove nonStrictJavadocProjects
This become obsolete with 1a3067c89 ("Enable werror for javadoc and
fix javadoc issues"), which enabled 'werror' for javadoc on all
projects.
2020-04-17 10:29:40 +02:00
Florian Schmaus
0f7b7df1f0 muc: fix roomDestroyed() callback
The previous site where the callback was invoked was only reached if
there was also a user status on the unvailable presence. But those are
not part of unavilable presences upon room destruction.

Fixes SMACK-888.
2020-04-16 21:59:19 +02:00
Florian Schmaus
20aaef2628 muc: remove 'joined' boolean from MultiUserChat, use myRoomJid instead
If myRoomJid is set, we are joined.
2020-04-16 21:44:55 +02:00
Florian Schmaus
26ab832452 muc: only call userHasLeft() at one site
There is no need to duplicate that code. Also ensure that
userHasLeft() is invoked *before* the listeners are invoked, so that
e.g. isJoined() returns false in the listeners.
2020-04-16 21:40:06 +02:00
Florian Schmaus
ab2822be3e muc: also set myRoomJid to null if we have left the room 2020-04-16 21:39:52 +02:00
Florian Schmaus
c519dd1213 muc: do check for equality twice
We already performed the presence.getFrom().equals(myRoomJID) check
and saved its result. No need to do it again.
2020-04-16 21:38:55 +02:00
Florian Schmaus
e2e228fc93 muc: synchronize Stats.create(Integer)
Since this method is used by the MUCUserProvider, it is potentially
invoked concurrently and the access to the statusMap must be
synchronized then.
2020-04-16 21:20:53 +02:00
Florian Schmaus
fa643f12d5 muc: add MUC status code 33
SMACK-882
2020-04-16 21:20:35 +02:00
Florian Schmaus
162651821e sinttest: log unexpected Throwables thrown by runTests()
Because we would not see those if the finally block also threw.
2020-04-15 19:48:39 +02:00
Florian Schmaus
da5f59a996 Remove superfluous ' "" +' statements
Using

sed -i 's; "" +;;' <file>

to remove those.
2020-04-15 09:35:13 +02:00
Florian Schmaus
77d12d4758 fastening: set ENABLED_BY_DEFAULT to false
If it is announced as feature, entities sending fastened messages
expect the recipient to react somehow on those. And this is not the
case if this is just enabled in Smack.

Hence we disable it per default and require smack users to explicitly
enable it after they have setup the according stanza listeners.
2020-04-15 09:34:04 +02:00
Florian Schmaus
cda764d62d Merge branch 'master' of github.com:igniterealtime/Smack 2020-04-15 09:30:04 +02:00
Florian Schmaus
4f3d89e666
Merge pull request #355 from vanitasvitae/messageFastening
Message fastening
2020-04-15 09:29:02 +02:00
adiaholic
38c77fd573 Correct erroneous documentation 2020-04-14 16:38:41 +05:30
Florian Schmaus
cbc2024875 sinttest: print smack version 2020-04-14 09:20:43 +02:00
Florian Schmaus
7f2e8b793a
Merge pull request #379 from akrherz/travis
update Travis-CI badge on README.md
2020-04-13 23:03:51 +02:00
Florian Schmaus
ad13effe41
Merge pull request #382 from vanitasvitae/typo
Fix typo in XmppElementUtil
2020-04-13 23:03:34 +02:00
Florian Schmaus
f3e93cef32 core: do not init() closingStreamReceived sync point in initState()
The initState() method is also called in disconnect(). And if we reset
the closingStreamReceived sync point at disconnect, it will break the
WaitForClosingStreamElementTest integration test.
2020-04-13 22:50:02 +02:00
Florian Schmaus
dd248adb28 sinttest: delcare boolean in WaitForClosingStreamElementTest 2020-04-13 22:49:31 +02:00
Florian Schmaus
6c3cd53567 pep: improve pep event filter 2020-04-13 22:37:54 +02:00
Florian Schmaus
7f027bd339 pep: Use EventItemsExtensionFilter 2020-04-13 22:37:54 +02:00
Florian Schmaus
2c6f444bab pubsub: Add EventItemsExtensionFilter 2020-04-13 22:37:54 +02:00
Florian Schmaus
50da46ffda core: Add ExtensionElementFilter 2020-04-13 21:27:31 +02:00
Florian Schmaus
5114f6dfa4 core: remove deprecated methods in PacketUtil
Those where deprecated in 2015 with 8409dddff ("Add
PacketUtil.extensionElementFrom()"), and in 2017 with
2288825b1 ("Retain smack-core API").
2020-04-13 21:25:28 +02:00
Florian Schmaus
fb7054bbe7 core: delete deprecated ToFilter
This filter was marked deprecated in 2017 with 5d0dd49e6 ("Introduce
ToMatchesFilter"), time to delete it.
2020-04-13 20:54:04 +02:00
Florian Schmaus
988954a9db core: delete deprecated filters
Those where deprecated in 2015 with d4a6d8e65 ("Rename
PacketFilter (and implementing classes) and PacketExtension"), now it
is time to delete them.
2020-04-13 20:52:07 +02:00
Florian Schmaus
aea95d3401 sinttest: also check for length of subdescriptions varargs 2020-04-13 20:46:59 +02:00
Florian Schmaus
c49999b933 core: add shortcut via hash in EqualsUtil
Return false as soon as the hashed value does not match. This is
sound, since every class that implements equals(Object) should also
implement hashCode().
2020-04-13 20:40:10 +02:00
Florian Schmaus
4f609b855c geoloc: make GeoLocation implement hashCode() and equals(Object) 2020-04-13 20:40:10 +02:00
Florian Schmaus
f5c412a98f geoloc: GeoLocation constructor should have Builder as sole paramter
Also remove that (broken) "Error and accuracy set" warning, but
mark (get|set)Error() as deprecated.
2020-04-13 20:39:43 +02:00
905d5dc102
Fix typo in XmppElementUtil 2020-04-13 18:18:31 +02:00
e0f7ddf5a8
Add support for XEP-0422: Message Fastening
SMACK-884
2020-04-13 18:17:26 +02:00
72a9cb65a6 OriginIdElement: Add proper equals() method 2020-04-13 17:38:47 +02:00
Florian Schmaus
9b20e2efd8 sinttest: signal failure if geoloc element does not match 2020-04-13 15:28:17 +02:00
Florian Schmaus
6d9936a0a6 geoloc: do not set error in integration test
As error is deprecated. And should be marked as such.
2020-04-13 15:27:43 +02:00
Florian Schmaus
340bcb2d12 pep: improve API, add PepEventListener
The geoloc, mood and usertune PEP users showed a pattern. Instead of
repeating this pattern every time, let PepManager do the hard work
2020-04-13 15:26:46 +02:00
Florian Schmaus
7c2f9e3603 pep: cleanup pep users API
Use EntityBareJid just as its done within PepManager. There is no need
for AsyncButOrdered in the PEP user managers, as PepManager already
takes care of that. Also the message carrying the PEP event should
always be the last parameter of the callbacks, as it is the least
important piece of information.
2020-04-13 12:14:32 +02:00
akrherz
d0a393118c
update Travis-CI badge on README.md 2020-04-11 10:36:58 -05:00
155 changed files with 5449 additions and 3411 deletions

View file

@ -4,8 +4,7 @@ android:
components:
- android-19
jdk:
- oraclejdk8
- openjdk9
- openjdk8
- openjdk11
before_cache:

View file

@ -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
-----

View file

@ -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"

View file

@ -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`

View file

@ -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
--------------------------

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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 &quot;RS145&quot;.
* PacketFilter myFilter = new PacketFilter() {
* public boolean accept(Packet packet) {
* return &quot;RS145&quot;.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 {
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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 {
/**
*

View file

@ -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;

View file

@ -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));

View file

@ -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.
*

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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 + "'");
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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.");
}
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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++) {

View file

@ -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");
}
}

View file

@ -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();
}
}

View file

@ -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>";

View file

@ -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'>"

View file

@ -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>";

View file

@ -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()

View file

@ -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));
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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

View file

@ -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);

View file

@ -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 {

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}
}
/**

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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();

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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);

View file

@ -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();
}
}
/**

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
/**

View file

@ -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);
}

View file

@ -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();

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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.
*

View file

@ -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();

View file

@ -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;
/**

View file

@ -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

View file

@ -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;
}

View file

@ -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;
/**

View file

@ -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
*

View file

@ -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
*/

View file

@ -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;
/**

View file

@ -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()}.

View file

@ -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

View file

@ -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
*/

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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