Compare commits

...

133 Commits

Author SHA1 Message Date
Florian Schmaus 01cd0507b6 Smack 4.5.0-alpha2-SNAPSHOT 2022-03-02 22:54:05 +01:00
Florian Schmaus ea601d0f9f Smack 4.5.0-alpha1 2022-03-02 22:41:29 +01:00
Florian Schmaus f07c8919cd Smack 4.4.5
-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAmIft6pfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFKsxAgAnssnWKmJ/TZvUIY1wGhX7+6y+Bg6VyK6Izne1D5yGFEqQvfz+RK2zGSN
 PUcq0WmvIXgCJLO9gvtyA3IK79Ak9IixA//PmTiDRyQhlC3YOqtyzXw5WyLa+d4j
 VsfpfQ9AgPHRmViLMMvFmy+P0ozFx5v/ccV5iZV8zlxAr1d5mQ2/kyGumZODBOFR
 hJGyeOJEMV/1jjuDURmpnX4l2l3FvEmEXA0apZD4WXE55M0QUVZiPhbSUJnEGeD+
 nZ3eADgaqi/giIcYrlnYWTeWT1++szNmbOdRhiWRxtEjLM6sXvsOAbP9kY9LUQCz
 WWocLYfe1fTHd91kRbIeo7hM+YMojw==
 =PUGk
 -----END PGP SIGNATURE-----

Merge tag '4.4.5'

Smack 4.4.5
2022-03-02 22:40:30 +01:00
Florian Schmaus cccf3ce5f3 Smack 4.4.5 2022-03-02 19:30:01 +01:00
Florian Schmaus 229a6edb1b Switch to CHANGELOG.md
Bye Bye changelog.html. You have served us good old friend. But JIRA
does no longer create compatible HTML changelogs. And markdown seems
to be the better alternative anyway.
2022-03-02 18:48:31 +01:00
Florian Schmaus a15110694a Merge branch '4.4' 2022-02-18 21:30:42 +01:00
cmeng-git 4d026d8ae8 [jingle] Add element and text to JingleReason's XML 2022-02-18 21:29:32 +01:00
Florian Schmaus 3885153ea1
Merge pull request #518 from Flowdalic/ssl-context-config
SSLContext config
2022-02-18 21:28:31 +01:00
Florian Schmaus ba2e36dbc3 [core] Deprecate some SSLContext config options and add KeyManager option
Smack historically provided fine-grained options for the
SSLContext. This is however not flexible enough, as an option to
specifiy the KeyManager(s) was missing.

This deprecated the options for keystore path, keystore type, and
PKCS#11 library, in favor of an option to set the KeyManager, which
could be aware of the keystore path and type, and the PKCS#11
library. At some point, Smack may provide some high-level methods to
create a KeyManager from provided keystore path, keytsore type and
PKCS#11 library.
2022-02-17 12:17:54 +01:00
Florian Schmaus 4622d00d9e [tcp] Unravel SSLSocketFactory.createSocket() invocation 2022-02-17 11:58:02 +01:00
Florian Schmaus cd70e7aa5a
Merge pull request #513 from maniac103/tone-down-roster-error-logging
Conditionally reduce severity of roster reload error logging
2022-02-15 17:01:25 +01:00
Danny Baumann aafc24a966 Conditionally reduce severity of roster reload error logging
If the roster feature is not supported by the server, there's no need to
log this at SEVERE log level.
2022-02-15 12:21:27 +01:00
Florian Schmaus 91a1825eb8
Merge pull request #514 from Mtodiy/log_uploading_failure
Add warning log to see what http connection is having trouble
2022-02-10 16:54:24 +01:00
Micha 1b0e19f699 Add custom HttpUploadExceptions for more information in case of IOExceptions 2022-02-10 10:43:05 +01:00
Florian Schmaus 4a83d2957e Merge branch '4.4' 2022-02-06 17:48:08 +01:00
Florian Schmaus 4e715a35e5
Merge pull request #512 from bgrozev/fix-smack-reactor-select
fix: Correctly handle the case when the action due time is 0.
2022-02-06 17:47:50 +01:00
Boris Grozev 67a5c3a41a [core] Correctly handle due time of '0' in SmackReactor
Scheduled actions that are due in 0 milliseconds are due
immediately. Otherwise we would invoke select() with 0.

Fixes SMACK-923.
2022-02-06 17:38:50 +01:00
Florian Schmaus ad9867ebdf Merge branch '4.4' 2022-02-03 08:43:32 +01:00
Florian Schmaus 5cd7a6c60e
Merge pull request #509 from Flowdalic/jingle-reason-extension-element
Jingle reason extension element
2022-02-03 07:58:29 +01:00
Florian Schmaus e25be8fea4
Merge pull request #510 from Flowdalic/future-invoke-at-most-once
[SmackFuture] Invoke the callbacks at most once
2022-02-03 07:58:19 +01:00
Florian Schmaus 3ff553549a [SmackFuture] Invoke the callbacks at most once
Previously, if a SmackFuture both was successful and unsuccessful, it
was possible that the onSuccess() callback was invoked twice.

Reported-by: Boris Grozev <boris@jitsi.org>
2022-02-02 20:47:31 +01:00
cmeng-git 4cdb4acf26 [core] Fix IQProvider javadoc
It is IqProvider that should be used instead.
2022-02-02 12:13:25 +01:00
Florian Schmaus 7fad14ac0c [jingle] Improve Jingle <reason/> support
Fixes SMACK-922.
2022-02-02 12:10:53 +01:00
Florian Schmaus c06cf72337 [core] Support IqProvider in SmackTestUtil 2022-02-02 10:41:00 +01:00
Florian Schmaus 0e637068e6 [core] Factor PacketParserUtils.parseIqData() in extra method 2022-02-02 10:41:00 +01:00
Florian Schmaus 1dae0c0c32 [core] Use Enum.toString() in XmlStringBuilder.attribute(String, Enum<?>)
All other enum-using methods of XmlStringBuilder already use
Enum.toString(), as opposed to Enum.name(), this was the only left. I
do not remember why I did not to change this method too, probably
because of its plenty call sites.

But since this method already broke Jingle XML serializaton,
JingleAction was e.g., 'session_accept' when it should be
'session-accept', we change it now.

Fixes SMACK-921.
2022-01-22 11:08:35 +01:00
Florian Schmaus 1a727c152e [core] Improve warning message of ExceptionThrowingCallbackWithHint 2022-01-22 10:24:01 +01:00
Florian Schmaus 5f75d141ff Merge branch '4.4' 2021-12-27 21:25:24 +01:00
Florian Schmaus 5c46451cd2 [xdata] Add BooleanFormField.getValueAsBooleanOrNull()
This method is meant to provide 'raw' access to what has been sent
over the wire. It is assumed to be not of much use in the typically
case, but provided for completeness.
2021-12-27 21:24:19 +01:00
Florian Schmaus 940d7bf02a [xdata] Adjust behavior of BooleanFormField.getValueAsBoolean()
According to XEP-0004 § 3.3, the default value of a boolean form field
is 'false'. And since users are typically interested in getting the
value, and not potentially 'null' as result, we adjust the behavior of
the getValueAsBoolean() method consider the default value.
2021-12-27 21:17:57 +01:00
Florian Schmaus 45a4987818 [smack-debug] Factor duplicate code into its own method 2021-12-22 09:39:16 +01:00
Florian Schmaus ae2394ea83
Merge pull request #507 from guusdk/enhanced-debugger-performance
Increase performance of EnhancedDebugger
2021-12-22 09:16:46 +01:00
Guus der Kinderen 8be1568a5d Increase performance of EnhancedDebugger
When using the Enhanced Debugger, a UI application is very noticably slow, especially around establishing a new
session. Profiling shows that much of the overhead is caused by XMPP-data being added to the text areas that are
on the "raw sent packets" and "raw received packets" tabs of the debugger.

This commit improves performance (considerably) by:
- properly limiting the amount of lines in those text areas (this was intended but broken in the old implementation)
- buffering data to be added in batches, to reduce the amount of invocations to JTextChat.append()

As an aside: some newline-based formatting was removed. As the provided data is now already formatted, retaining that
did not make much sense anymore.
2021-12-21 15:48:30 +01:00
Florian Schmaus 178e82a3aa
Merge pull request #506 from Flowdalic/gssapi-no-password
Prevent password enforcement for SASL GSSAPI
2021-12-21 13:37:31 +01:00
Florian Schmaus 817dc0ed3a Prevent password enforcement for SASL GSSAPI
Similar fix as 9b339efbc1 ("Prevent password enforcement for SASL
anonymous") just for SASL GSSAPI. Fixes SMACK-920.

Fixes: 92f4aadfdc ("[sasl] Avoid mechanisms that need a password when none is available")
2021-12-21 12:53:10 +01:00
Florian Schmaus b3637101ce [sinttest] Use MultiUserChat.getMyRoomJid() in integration test 2021-12-20 22:40:55 +01:00
Florian Schmaus e530db2e6d [muc] Add MultiUserChat.getMyRoomJid() 2021-12-20 22:40:37 +01:00
Florian Schmaus 56ca31b156 [sinttset] AbstractMultiUserChatIntegrationTest cleanups 2021-12-20 22:40:13 +01:00
Florian Schmaus 56c66f17d8 [sinttest] MultiUserChatEntityIntegrationTest cleanups 2021-12-20 22:39:45 +01:00
Dan Caseley e51cf47b29 [sinttest] Additional tests for § 6 of XEP-0045
Modified-by: Florian Schmaus <flo@geekplace.eu>
2021-12-20 21:11:31 +01:00
Florian Schmaus 993a307222 Add o.j.smackx.softwareinfo.form.SoftwareInfoForm to startup classes
This ensures that the form fields are registered.
2021-12-17 09:51:19 +01:00
Florian Schmaus 182d01a4b0 [softwareinfo] Register urn:xmpp:dataforms:softwareinfo's field types 2021-12-15 20:17:23 +01:00
Florian Schmaus e1845a52ca [formtypes] Add FormFieldRegistry.register(String, FormField.Type, String...) 2021-12-15 20:17:23 +01:00
Florian Schmaus 89ef46525e Merge branch '4.4' 2021-12-13 21:51:42 +01:00
Florian Schmaus fc7fc10c69 [pubsub] Allow for character data before <item/>'s payload
Fixes SMACK-918.
2021-12-13 21:50:10 +01:00
Florian Schmaus e39adff40f [muc] Only notify() about processed self-presence once
Since notify() is a rather expensive operation, we should only invoke
it once. Especially since some servers include 110 in all self
presences, not just the initially reflected one on MUC join.
2021-12-13 21:20:40 +01:00
Florian Schmaus d1273532ce [muc] Correctly processes self-presences
The change in 52a49769f9 ("[muc] Check for self-presence first in
presence listener") caused all self-presences (MUC user status 110) to
be ignored in the further processing chain eventually invoking
checkRoleModifications() and checkAffiliationModifications(). However,
some servers (e.g., ejabberd) include 110 not only in the initial
presence but in all following, and we ant to process these.

Fixes SMACK-918

Fixes: 52a49769f9
2021-12-13 21:15:30 +01:00
Florian Schmaus 10a2687ff1 [sinttest] Allow the selection of individual test *methods* 2021-12-13 21:08:45 +01:00
Florian Schmaus 69a55aaa84 [sinttest] Evaluate compatibilityMode property 2021-12-13 21:04:32 +01:00
Florian Schmaus def7c91e6a [sinttest] Introduce dirty hack in performActionAndWaitForPresence() 2021-12-13 21:03:40 +01:00
Florian Schmaus 4e2d0035ac [sinttest] Increase 'try' block in MultiUserChatRolesAffiliations*Test
Fixes: aff6283798
2021-12-13 21:01:41 +01:00
Florian Schmaus 400a2b2564 [sinttest] Use popular desktop OS in SoftwareInfoIntegrationTest values 2021-12-13 20:51:33 +01:00
Florian Schmaus 33e8053258 [sinttest] Remove unnecessary catch in UserTuneIntegrationTest 2021-12-13 20:50:56 +01:00
Guus der Kinderen 819ed87a17 [sinttest] Wait for notification filter to propagate
The UserTuneIntegrationTest, in rapid succession:
- add a listener for PEP-published usertune data
- publishes a usertune
- waits for a notification to arrive

Implicit to adding the listener is the publication of a change in
Pubsub notification filtering. This can involve a stanza handshake,
as CAPS is involved.

A race condition exists where the usertune data can be published
before the notification filter has been properly applied.

The changes in this commit add a synchronzation point that ensures
that the notification filter is in place, before the usertune data
is published.

Co-authored-by: Paul Schaub <vanitasvitae@fsfe.org>
2021-12-12 18:13:31 +01:00
Florian Schmaus e3c50aeeb7 Bump PGPainless to 1.0.0-rc6 2021-12-07 19:29:08 +01:00
Florian Schmaus 11cc2d8d77 [core] Align behavior of getParserFor(InputStream) and getParserFor(Reader)
Reported-by: Ingo Bauersachs <ingo@jitsi.org>
2021-11-30 10:49:55 +01:00
Florian Schmaus cad63bc107 Merge branch '4.4' 2021-11-09 15:30:02 +01:00
Florian Schmaus 8736e3e434 [core] Remove erroneous "assert !connected" in connect()
Connections like XMPPTCPConnection may still reported connected, if
they are, for example disconnected but resumable. This is already
accounted for in throwAlreadyConnectedExceptionIfAppropriate(), hence
the assert is unnecessary and leads to false negatives for
XMPPTCPConnection.

For the sake of completeness, the right condition, assuming
XMPPTCPConnection is used, for the assert would be:

	assert !connected || disconnectedButResumable;
2021-11-05 10:19:26 +01:00
Florian Schmaus 61d56aa06a
Merge pull request #441 from guusdk/remove-caps-changed-listener
Add method to remove registered CAPS listener
2021-11-04 13:30:19 +01:00
Guus der Kinderen fdffd58d25 Add method to remove registered CAPS listener
ServiceDiscoveryManager allows a listener for CAPS changes to be
registered. It should also allow to remove a previously registered
listener.
2021-11-04 12:29:27 +01:00
Florian Schmaus f611941dcf
Merge pull request #480 from guusdk/sint_improve-notinroster
[sint] increase stability
2021-11-04 09:46:18 +01:00
Florian Schmaus f2c2f45f18 [jiveproperties] Use DoOnce utility to log "JavaObject not enabled" 2021-11-01 19:28:15 +01:00
Florian Schmaus 7dc8cc176e [core] Add DoOnce utility 2021-11-01 19:27:09 +01:00
Florian Schmaus 2628dc328c Smack 4.4.4
-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAmGAG6lfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFJdGwf+NmDZF8OtpvEI/TRSpDfyQxDVPbXi8Y8Z7E0CbNLHVkn7CQl/HcYsJkjL
 IGLRqUCNztejNbSvaNl9Zds4u2StBeTgEXbPwbT8UdJU8Ji+jqflJYW53QyNc1Kt
 AToAKHW1m2xamMpqgqnHQsQxUOHXp5VIEv7/dRVQLyIL0F8TaMlMPRpI65seOS3v
 VQj66eskVuuwib5q1n8DBwKVhB417UsTEIAMxZ5Zxdcs8YwB+YML4KuxsSZchTyt
 b6tuZnLEMxFPUUAIMlBNoslPBuj6GoP+LNFotnS1kDfK6yMOaeGSlLVaxRRSzqVx
 VVnHSiSZeEuzfw55ymTEewr42tD47A==
 =LrgC
 -----END PGP SIGNATURE-----

Merge tag '4.4.4'

Smack 4.4.4
2021-11-01 18:10:53 +01:00
Florian Schmaus 403890d988 Smack 4.4.5-SNAPSHOT 2021-11-01 18:10:35 +01:00
Florian Schmaus 5731e61782 Smack 4.4.4 2021-11-01 17:54:01 +01:00
Florian Schmaus 22cf7bace8 [resources] Rename get-contributors.sh to generate-notice-file 2021-11-01 17:53:01 +01:00
Florian Schmaus 18b25902c4
Merge pull request #505 from vanitasvitae/bumpPgpainless
Bump PGPainless to 0.2.19
2021-11-01 17:27:07 +01:00
Paul Schaub 53d385ab93
Bump PGPainless to 0.2.19 2021-10-29 21:44:32 +02:00
Florian Schmaus fa88f78232 Merge branch '4.4' 2021-10-25 17:05:12 +02:00
Florian Schmaus 22b2efc6a6 Update NOTICE file 2021-10-25 17:00:14 +02:00
Florian Schmaus 7fd300888e
Merge pull request #504 from JonathanLennox/StanzaBuilder-remove-extension
Add removeExtension methods to StanzaBuilder.
2021-10-25 16:58:49 +02:00
Jonathan Lennox 4ae3fbb073 Add removeExtension methods to StanzaBuilder. 2021-10-25 14:50:45 +00:00
Florian Schmaus 447c1304cf
Merge pull request #503 from jitsi/bugfix/4.4/bosh-connection2
Add missing stream namespace to xml declaration
2021-10-21 13:35:09 +02:00
Ingo Bauersachs d8ce2d335b Add missing stream namespace to xml declaration
Fixes 7199003f98
2021-10-21 09:14:52 +02:00
Florian Schmaus 8c87daa7b4 Merge branch '4.4' 2021-10-20 19:03:38 +02:00
Florian Schmaus d0be6a6216 [build] Remove OSS Sonatype Snapshot repository
Having the OSS Sonatype Snapshot repository searched causes build
failures if there snapshot dependencies are resolved due the javadoc
linking:

javadoc: error - Error fetching URL: https://jxmpp.org/releases/1.0.3-SNAPSHOT/javadoc/
> Task :javadocAll
javadoc: error - Error fetching URL: https://minidns.org/releases/1.0.2-SNAPSHOT/javadoc/
2 errors
2021-10-20 19:02:19 +02:00
Florian Schmaus 6d0a0ff2f4 Merge branch '4.4' 2021-10-20 16:29:45 +02:00
Florian Schmaus f03f2c75f8
Merge pull request #502 from abyss638/accept_empty_form_label_backport_from_master
Accept empty string as form field label value (backport to 4.4)
2021-10-20 16:29:14 +02:00
Simon Abykov 19270a2eca Accept an empty string as the label value 2021-10-20 10:12:29 +01:00
Florian Schmaus bd528d2c32
Merge pull request #493 from abyss638/accept_empty_form_label
Accept empty string as form field label value
2021-10-19 20:46:55 +02:00
Simon Abykov 1956048811 Accept an empty string as the label value 2021-10-19 17:34:13 +01:00
Florian Schmaus e842195b71 Merge branch '4.4' 2021-10-19 14:33:21 +02:00
Florian Schmaus ab92bc4b40
Merge pull request #501 from Flowdalic/fix-redundant-namespaces
Fix redundant namespaces
2021-10-19 14:29:18 +02:00
Florian Schmaus 0fa6d88575
Merge pull request #500 from jitsi/bugfix/4.4/error-stanzagetter
Add getter for the stanza associated with the exception
2021-10-19 14:29:11 +02:00
Florian Schmaus a0f97707d8
Merge pull request #499 from jitsi/feature/4.4/osgi
Make Smack jars OSGi bundles
2021-10-19 14:28:59 +02:00
Florian Schmaus 7199003f98
Merge pull request #498 from jitsi/bugfix/4.4/bosh-connection
Fix BOSH connection establishment
2021-10-19 14:10:44 +02:00
Florian Schmaus 9fb7d6d97c
Merge pull request #497 from jitsi/bugfix/4.4/sasl-anonymous
Prevent password enforcement for SASL anonymous
2021-10-19 14:09:39 +02:00
Florian Schmaus ad6e285346
Merge pull request #496 from damencho/fix-destroy-4.4
fix: Fixes processing destroy unavailable presence.
2021-10-19 14:09:27 +02:00
Florian Schmaus dac8b728b4
Merge pull request #492 from Flowdalic/start-local-socks5-proxy
Start local SOCKS5 proxy
2021-10-19 14:08:57 +02:00
Florian Schmaus 2ff53abef3
Merge pull request #494 from Flowdalic/mux-race-condition
[muc] Check for self-presence first
2021-10-19 14:08:32 +02:00
Florian Schmaus d25bd811dc
Merge pull request #491 from Flowdalic/assert-not-end-document
[core] Assert that 'event' is not END_DOCUMENT in forwardToEndTagOfDepth()
2021-10-19 14:08:16 +02:00
Florian Schmaus 6f67553fcf [jingle] Add unit test to check that there are no redundant namespaces
Related to SMACK-917.
2021-10-19 11:34:07 +02:00
Florian Schmaus 453ca6aeb0 [jingle] Make Jingle.Builder extend IqBuilder
This makes Jingle.Builder to follow the new IqBuilder pattern,
allowing to construct Jingle IQs with a given stanza ID (mostly
useful for unit tests).
2021-10-19 11:32:51 +02:00
Florian Schmaus a3840659aa [jingle] Mimic Manager.connection() in JingleTransportManager
Eventually JingleTransportManager should be a subclass of Manager (or
be replaced by Manager), as JingleTransportManager holds a strong
reference to the XMPPConnection. This could cause memory leaks. But
for now, we mimic the Manager API in JingleTransportManger to make a
future transition to Manager easier.
2021-10-19 11:31:08 +02:00
Florian Schmaus b243a40e26 [core] Pass down the XML environment in IQChildElementXmlStringBuilder
This allows to avoid redundant XML namespaces within IQs, like for
example here:

<iq xmlns='jabber:client' id='EKP8I-1' type='set'>
    <jingle xmlns='urn:xmpp:jingle:1' action='content-accept' sid='MySession'>
        <content xmlns='urn:xmpp:jingle:1' creator='initiator' name='Hello world'>
        </content>
    </jingle>
</iq>

Fixes SMACK-917

Reported-by: Jonathan Lennox
2021-10-19 11:29:23 +02:00
Florian Schmaus 585bcb4dc8 [jingle] Add empty element optimization for <content/> 2021-10-19 11:16:35 +02:00
Ingo Bauersachs 27ff37fa98 Add getter for the stanza associated with the exception
Fixes SMACK-916
2021-10-17 16:06:41 +02:00
Ingo Bauersachs b675aac81b Make Smack jars OSGi bundles
Fixes SMACK-343 by using bnd instead of the deprecated Gradle
plugin that was previously used and removed in commit
d06f533bb9.
2021-10-17 15:57:48 +02:00
Ingo Bauersachs 8074ddd60a Fix BOSH connection establishment
AbstractXMPPConnection waits for the flag lastFeaturesReceived since
57961a8cc1, but it is never set from
BOSH connections. Use parseFeaturesAndNotify instead of
parseFeatures to set the signal.

Similarly the XmlEnvironment is not set from bosh, but required in
ParserUtils.getXmlLang.
2021-10-17 15:00:04 +02:00
Ingo Bauersachs 9b339efbc1 Prevent password enforcement for SASL anonymous
requirePassword from 92f4aadfdc
already excludes SASL external, but missed SASL anonymous.
2021-10-16 21:09:01 +02:00
Дамян Минков 820adf8865 [muc] Also process destory message if it contains <status/>
Fixes SMACK-915
2021-10-13 07:04:32 -07:00
Florian Schmaus 105c74b22b [muc] Call userHasLeft() *after* the leave presence was sent
Calling userHasLeft before sending the leave presence may result in
invalid state as the MUC presence lister may modify the MUCs local
state, e.g., occupantsMap, conurrently with the leave operation.

If we reset it after the leave presence was send and acknowledged,
then this can not happen as the server will not longer send any MUC
related presences to us.

Also fixes SMACK-914. In theory 52a49769f9 ("[muc] Check for
self-presence first in presence listener") alone would fix SMACK-914,
but this also fixes it indepentendly of 52a49769f9. Both commits are
sensible, so both are applied.
2021-10-11 09:09:20 +02:00
Florian Schmaus 52a49769f9 [muc] Check for self-presence first in presence listener
Fixes SMACK-914
2021-10-11 09:08:44 +02:00
Florian Schmaus ec456399b5 [carbons] Remove erroneous assert statement in connectionClosed()
The assert statement in CarbonManager's connectionClosed() connection
listener callback was erroneous. A connection may be connected, but
never was authenticated. If now the connection is closed, then
carbonsListener was never setup (via the authenticated() callback),
causing the assert to throw an exception.
2021-09-27 14:14:01 +02:00
Florian Schmaus ae4ff244a3 [muc] Check mucServicedDiscoInfo for null in serviceSupportsStableIds()
Fixes SMACK-913.
2021-09-27 13:56:15 +02:00
Florian Schmaus 49ad8c0954 [disco] Add DisocverInfo.nullSafeContainsFuture(DiscoverInfo, CharSequence) 2021-09-27 13:55:52 +02:00
Florian Schmaus b57cf8375f [socks5] Remove stale null check
The method getLocalStreamHost() does no longer return 'null', hence
the null check is unnecessary.
2021-09-13 18:25:44 +02:00
Florian Schmaus 09710b3203 [socks5] Fix javadoc of getLocalStreamHost()
The method does no longer return null.

Reported-by: Simon Abykov <simon.abykov@gmail.com>
2021-09-13 18:24:54 +02:00
Florian Schmaus 55299fb7e7 [core] Assert that 'event' is not END_DOCUMENT in forwardToEndTagOfDepth() 2021-09-13 10:10:55 +02:00
Florian Schmaus 8ae5ef1f51 [socks5] Ensure that the local SOCKS5 proxy is running (if enabled)
In 9352225f44 ("Rework SOCKS5 unit tests so that they can be run in
parallel") the call to getSocks5Proxy() in
Socks5BytestreamManager.getLocalStreamHost() was removed. Since
getSocks5Proxy() does also start the local proxy, if it is not already
running, this caused Smack to no longer automatically start the local
proxy.

This commit re-adds the call to getSocks5Proxy() and fixes SMACK-912.
2021-09-13 09:58:15 +02:00
Florian Schmaus 1564b98d60
Merge pull request #490 from JonathanLennox/update-securitymode-docs
Update documentation of default SecurityMode.
2021-09-10 09:04:52 +02:00
Jonathan Lennox f0a0796d33 Update documentation of default SecurityMode. 2021-09-09 17:28:25 +00:00
Florian Schmaus bc281d84a7
Merge pull request #487 from fenuz/feature/multiversion_mam_support
Add support for multiple versions of MAM
2021-08-26 17:15:38 +02:00
Frank Matheron 5b857a1b82 [mam] Support multiple versions of MAM
Support multiple version of MAM and do discovery of the version of MAM supported by
the archive. Currently supported MAM versions are v1 and v2.

Fixes SMACK-911

See https://discourse.igniterealtime.org/t/mam-support-of-multiple-mam-versions/90136
2021-08-26 16:49:06 +02:00
Florian Schmaus 3f2418dec7 [core] Fix spelling errors in code comment within PacketParserUtils 2021-08-25 22:54:23 +02:00
Florian Schmaus c13f4ccac3 [mam] Fix MamPrfsIQProviderTest
Parsers handed over to IQ providers should be positioned at the IQ
child element when being invoked. Therefore we remove the wrapping
<iq> in some test XML.

Also make checkMamPrefsIQProvider() a paramterized test.
2021-08-25 22:52:26 +02:00
Florian Schmaus 613f1afcab [mam] Delete unused local variable 'iqType' in MamPrefsIQProvider 2021-08-25 22:48:31 +02:00
Florian Schmaus 001985647a Merge branch '4.4'
This also fixes a errornous merge where the same branch with different
commit was merged into master and 4.4

The conflicting commits are

4.4:
8f760eaeb3  getRawValueCharSequences
e626580f68

master:
b47225c2c1  getRawValues
097d245358
2021-08-23 18:02:00 +02:00
Florian Schmaus 0d73c21945 [pubsub] FormNode(Provider) should not fail if there is no DataForm
Error IQ respones may not contain a data form, e.g.

<iq type="error" id="6LXNC-48" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test/5dsi4g084a">
  <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
    <configure node="fdp/submitted/spot_report"/>
  </pubsub>
  <error code="403" type="auth">
    <forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  </error>
</iq>

Also FormNode's toXML() already handled the case where submitForm was
'null'. Only the constructor threw a IAE if submitForm was 'null'.

Fixes SMACK-910.

Closes: https://github.com/igniterealtime/Smack/pull/471
2021-08-23 17:39:59 +02:00
Florian Schmaus 32a38c4e77 [core] Check if the successor vertex exists in StateDescriptorGraph
If the successor's module is disabled then the vertex may be null. In
this case, we can simple continue with the next successor in the list.

Previously, due to d33a5a23c3 ("[core] Introduce
Builder.failOnUnknownStates() and unit tests") this would trigger an
assert in addOutgoingEdge().

Fixes: d33a5a23c3 ("[core] Introduce Builder.failOnUnknownStates() and unit tests")
2021-08-22 16:30:30 +02:00
Florian Schmaus a0b9279441 [sinttest] Add "compatibility mode" setting 2021-08-22 16:16:03 +02:00
Dan Caseley 48c057ab10 [sinttest] Additional tests for s5 of XEP-0045
Modified-by: Florian Schmaus <flo@geekplace.eu>
2021-08-22 15:29:07 +02:00
Florian Schmaus 98d530819f
Merge pull request #488 from vanitasvitae/bumpPgpainless
OpenPGP: Bump PGPainless to 0.2.8
2021-08-10 16:49:46 +02:00
Paul Schaub 9e33fc56e1
Bump PGPainless to 0.2.8 2021-08-10 15:23:49 +02:00
Paul Schaub 9c5b0a2a16
Bump Bouncycastle to 1.69 2021-08-10 14:51:52 +02:00
Florian Schmaus f39e433121
Merge pull request #484 from guusdk/SMACK-908_debugger-tabs_4.4-branch
SMACK-908: Don't use components to count tabs in Debugger
2021-08-03 21:31:38 +02:00
Florian Schmaus d7c708b167
Merge pull request #486 from Flowdalic/4.4-form-fields-raw
Provide and use the raw values of form fields
2021-08-03 21:29:03 +02:00
Florian Schmaus 8f760eaeb3 [caps] Use the raw character data of form fields when caclulating the hash
Fixes SMACK-909.
2021-07-19 15:38:42 +02:00
Florian Schmaus e626580f68 [xdata] Safe the raw character data of form field values
Related to SMACK-909.
2021-07-19 15:38:42 +02:00
Guus der Kinderen 58774ad05b SMACK-908: Don't use components to count tabs in Debugger
Although the amount of components in a JTabbedPane apparently is often equal to the amount of tabs in it, that need not be the case.
2021-07-08 15:34:16 +02:00
Florian Schmaus 00249f3a67 Smack 4.4.4-SNAPSHOT 2021-07-06 14:51:00 +02:00
Guus der Kinderen 5e203086b3 [sint] increase stability
Some roster-based tests depend on there not being any prior subscription state beteween entities. The utility method that
tries to guarantee that, acts on the state of the roster that's cached in memory, but acts on the one that's stored on the
server. This occasionally causes issues, as both representations might be different.

Stability is added in this commit by:
- refreshing the roster from the server prior to evaluating it
- ignoring an 'item-not-found' as returned by the server, when the code tries to remove that item.
2021-06-25 16:08:31 +02:00
118 changed files with 4989 additions and 2402 deletions

1855
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

6
NOTICE
View File

@ -29,6 +29,7 @@ Chris Deering
Christoph Fiehe
Craig Hesling
Damian Minkov
Dan Caseley
Daniele Ricci
Daniel Henninger
Daniel Hintze
@ -44,6 +45,7 @@ Fernando Ramirez
Florian Kimmann
Florian Schmaus
Francisco Vives
Frank Matheron
Gaston Dombiak
Georg Lukas
Gilles Cornu
@ -64,6 +66,7 @@ Jay Kline
Jeff Williams
Jesus Fuentes
John Haubrich
Jonathan Lennox
Júlio Cesar Bueno Cotta
Lars Noschinski
Luca Stucchi
@ -82,6 +85,7 @@ Pete Matern
Piotr Nosek
Rajat Kumar Gupta
Robin Collier
Simon Abykov
Simon Schuster
Son Goku
Tairs Rzajevs
@ -98,4 +102,4 @@ V Lau
Vyacheslav Blinov
Wolf Posdorfer
Xiaowei YAN
Yash Thakkar
Yash Thakkar

View File

@ -6,6 +6,7 @@ buildscript {
}
dependencies {
classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2'
classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.0.0"
}
}
@ -147,7 +148,7 @@ allprojects {
smackMinAndroidSdk = 19
junitVersion = '5.7.1'
commonsIoVersion = '2.6'
bouncyCastleVersion = '1.68'
bouncyCastleVersion = '1.69'
guavaVersion = '30.1-jre'
mockitoVersion = '3.7.7'
orgReflectionsVersion = '0.9.11'
@ -196,10 +197,6 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
// Add OSS Sonatype Snapshot repository
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots'
}
}
tasks.withType(JavaCompile) {
@ -450,6 +447,7 @@ subprojects {
apply plugin: 'signing'
apply plugin: 'checkstyle'
apply plugin: 'org.kordamp.gradle.clirr'
apply plugin: 'biz.aQute.bnd.builder'
checkstyle {
toolVersion = '8.27'
@ -612,9 +610,15 @@ project(':smack-omemo').clirr.enabled = false
project(':smack-omemo-signal').clirr.enabled = false
subprojects*.jar {
manifest {
from sharedManifest
}
manifest {
from sharedManifest
}
bundle {
bnd(
'-removeheaders': 'Tool, Bnd-*',
'-exportcontents': '*',
)
}
}
configure(subprojects - gplLicensedProjects) {

View File

@ -86,6 +86,27 @@ debugger=console
The framework will first load the properties file from `~/.config/smack-integration-test/properties`
### Running selected tests only
Using `enabledTests` is is possible to run only selected tests. The
tests can be selected on a per class base or by specifying concrete
test methods. In the latter case, the methods must be qualified by a
(simple) class name.
For example:
```bash
$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest.test
```
will only run the `test()` method of `SoftwareInfoIntegrationTest`, whereas
```bash
$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest
```
would run all tests defined in the `SoftwareInfoIntegrationTest` class.
Overview of the components
--------------------------

38
resources/generate-notice-file Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
SMACK_DIR=$(realpath "${SCRIPT_DIR}/..")
cd "${SMACK_DIR}"
TEMPFILE=$(mktemp)
cleanup() {
rm "${TEMPFILE}"
}
trap cleanup EXIT
git shortlog -s |\
cut -f2- |\
grep -v '(no author)' |\
grep '\w \w.*' |\
sort \
> "${TEMPFILE}"
readonly NOTICE_FILE="${SMACK_DIR}/NOTICE"
cat <<EOF > "${NOTICE_FILE}"
Smack
An open-source XMPP library
maintained by Florian Schmaus
https://igniterealtime.org/projects/smack
Authors:
EOF
cat "${TEMPFILE}" >> "${NOTICE_FILE}"

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
git shortlog -s |\
cut -f2- |\
grep -v '(no author)' |\
grep '\w \w.*' |\
sort

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@ import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.util.CloseableUtil;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.igniterealtime.jbosh.AbstractBody;
import org.igniterealtime.jbosh.BOSHClient;
@ -200,6 +201,14 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
+ getHost() + ":" + getPort() + ".";
throw new SmackException.SmackMessageException(errorMessage);
}
try {
XmlPullParser parser = PacketParserUtils.getParserFor(
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
onStreamOpen(parser);
} catch (XmlPullParserException | IOException e) {
throw new AssertionError("Failed to setup stream environment", e);
}
}
@Override
@ -511,7 +520,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
parseAndProcessStanza(parser);
break;
case "features":
parseFeatures(parser);
parseFeaturesAndNotify(parser);
break;
case "error":
// Some BOSH error isn't stream error.

View File

@ -1,3 +1,8 @@
// Note that this is also declared in the main build.gradle for
// subprojects, but since evaluationDependsOnChildren is enabled we
// need to declare it here too to have bundle{bnd{...}} available
apply plugin: 'biz.aQute.bnd.builder'
description = """\
Smack core components."""
@ -55,3 +60,11 @@ task createVersionResource(type: CreateFileTask) {
}
compileJava.dependsOn(createVersionResource)
jar {
bundle {
bnd(
'DynamicImport-Package': '*',
)
}
}

View File

@ -524,9 +524,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
closingStreamReceived = false;
streamId = null;
// The connection should not be connected nor marked as such prior calling connectInternal().
assert !connected;
try {
// Perform the actual connection to the XMPP service
connectInternal();

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2017-2020 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2017-2022 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,6 +32,7 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@ -189,7 +190,7 @@ public abstract class ConnectionConfiguration {
protected ConnectionConfiguration(Builder<?, ?> builder) {
try {
smackTlsContext = getSmackTlsContext(builder.dnssecMode, builder.sslContextFactory,
builder.customX509TrustManager, builder.keystoreType, builder.keystorePath,
builder.customX509TrustManager, builder.keyManagers, builder.sslContextSecureRandom, builder.keystoreType, builder.keystorePath,
builder.callbackHandler, builder.pkcs11Library);
} catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | CertificateException
| KeyStoreException | NoSuchProviderException | IOException | NoSuchMethodException
@ -252,7 +253,7 @@ public abstract class ConnectionConfiguration {
}
private static SmackTlsContext getSmackTlsContext(DnssecMode dnssecMode, SslContextFactory sslContextFactory,
X509TrustManager trustManager, String keystoreType, String keystorePath,
X509TrustManager trustManager, KeyManager[] keyManagers, SecureRandom secureRandom, String keystoreType, String keystorePath,
CallbackHandler callbackHandler, String pkcs11Library) throws NoSuchAlgorithmException,
CertificateException, IOException, KeyStoreException, NoSuchProviderException,
UnrecoverableKeyException, KeyManagementException, UnsupportedCallbackException,
@ -266,69 +267,10 @@ public abstract class ConnectionConfiguration {
context = SSLContext.getInstance("TLS");
}
KeyStore ks = null;
PasswordCallback pcb = null;
KeyManager[] kms = null;
if ("PKCS11".equals(keystoreType)) {
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library;
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8));
Provider p = (Provider) c.newInstance(config);
Security.addProvider(p);
ks = KeyStore.getInstance("PKCS11", p);
pcb = new PasswordCallback("PKCS11 Password: ", false);
callbackHandler.handle(new Callback[] { pcb });
ks.load(null, pcb.getPassword());
} else if ("Apple".equals(keystoreType)) {
ks = KeyStore.getInstance("KeychainStore", "Apple");
ks.load(null, null);
// pcb = new PasswordCallback("Apple Keychain",false);
// pcb.setPassword(null);
} else if (keystoreType != null) {
ks = KeyStore.getInstance(keystoreType);
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
pcb = new PasswordCallback("Keystore Password: ", false);
callbackHandler.handle(new Callback[] { pcb });
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
} else {
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
try {
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
char[] password = "changeit".toCharArray();
try {
ks.load(stream, password);
} finally {
CloseableUtil.maybeClose(stream);
}
} catch (IOException e) {
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
ks = KeyStore.getInstance("jks");
// Open the stream again, so that we read it from the beginning.
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
try {
ks.load(stream, null);
} finally {
CloseableUtil.maybeClose(stream);
}
}
}
}
if (ks != null) {
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
if (kmf != null) {
if (pcb == null) {
kmf.init(ks, null);
} else {
kmf.init(ks, pcb.getPassword());
pcb.clearPassword();
}
kms = kmf.getKeyManagers();
}
// TODO: Remove the block below once we removed setKeystorePath(), setKeystoreType(), setCallbackHanlder() and
// setPKCS11Library() in the builder, and all related fields and the parameters of this function.
if (keyManagers == null) {
keyManagers = Builder.getKeyManagersFrom(keystoreType, keystorePath, callbackHandler, pkcs11Library);
}
SmackDaneVerifier daneVerifier = null;
@ -343,7 +285,7 @@ public abstract class ConnectionConfiguration {
}
// User requested DANE verification.
daneVerifier.init(context, kms, trustManager, null);
daneVerifier.init(context, keyManagers, trustManager, secureRandom);
} else {
final TrustManager[] trustManagers;
if (trustManager != null) {
@ -354,7 +296,7 @@ public abstract class ConnectionConfiguration {
trustManagers = null;
}
context.init(kms, trustManagers, null);
context.init(keyManagers, trustManagers, secureRandom);
}
return new SmackTlsContext(context, daneVerifier);
@ -405,7 +347,7 @@ public abstract class ConnectionConfiguration {
/**
* Returns the TLS security mode used when making the connection. By default,
* the mode is {@link SecurityMode#ifpossible}.
* the mode is {@link SecurityMode#required}.
*
* @return the security mode.
*/
@ -688,6 +630,8 @@ public abstract class ConnectionConfiguration {
public abstract static class Builder<B extends Builder<B, C>, C extends ConnectionConfiguration> {
private SecurityMode securityMode = SecurityMode.required;
private DnssecMode dnssecMode = DnssecMode.disabled;
private KeyManager[] keyManagers;
private SecureRandom sslContextSecureRandom;
private String keystorePath;
private String keystoreType;
private String pkcs11Library = "pkcs11.config";
@ -942,7 +886,12 @@ public abstract class ConnectionConfiguration {
* @param callbackHandler to obtain information, such as the password or
* principal information during the SASL authentication.
* @return a reference to this builder.
* @deprecated set a callback-handler aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
* {@link #setKeyManagers(KeyManager[])}, created by
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
*/
// TODO: Remove in Smack 4.6.
@Deprecated
public B setCallbackHandler(CallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
return getThis();
@ -960,7 +909,7 @@ public abstract class ConnectionConfiguration {
/**
* Sets the TLS security mode used when making the connection. By default,
* the mode is {@link SecurityMode#ifpossible}.
* the mode is {@link SecurityMode#required}.
*
* @param securityMode the security mode.
* @return a reference to this builder.
@ -970,6 +919,47 @@ public abstract class ConnectionConfiguration {
return getThis();
}
/**
* Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection.
*
* @param keyManagers an array of {@link KeyManager}s to initialize the {@link SSLContext} with.
* @return a reference to this builder.
* @since 4.4.5
*/
public B setKeyManagers(KeyManager[] keyManagers) {
this.keyManagers = keyManagers;
return getThis();
}
/**
* Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection.
*
* @param keyManager the {@link KeyManager}s to initialize the {@link SSLContext} with.
* @return a reference to this builder.
* @see #setKeyManagers(KeyManager[])
* @since 4.4.5
*/
public B setKeyManager(KeyManager keyManager) {
KeyManager[] keyManagers = new KeyManager[] { keyManager };
return setKeyManagers(keyManagers);
}
/**
* Set the {@link SecureRandom} used to initialize the {@link SSLContext} used by Smack to establish the XMPP
* connection. Note that you usually do not need (nor want) to set this. Because if the {@link SecureRandom} is
* not explicitly set, Smack will initialize the {@link SSLContext} with <code>null</code> as
* {@link SecureRandom} argument. And all sane {@link SSLContext} implementations will then select a safe secure
* random source by default.
*
* @param secureRandom the {@link SecureRandom} to initialize the {@link SSLContext} with.
* @return a reference to this builder.
* @since 4.4.5
*/
public B setSslContextSecureRandom(SecureRandom secureRandom) {
this.sslContextSecureRandom = secureRandom;
return getThis();
}
/**
* Sets the path to the keystore file. The key store file contains the
* certificates that may be used to authenticate the client to the server,
@ -977,7 +967,12 @@ public abstract class ConnectionConfiguration {
*
* @param keystorePath the path to the keystore file.
* @return a reference to this builder.
* @deprecated set a keystore-path aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
* {@link #setKeyManagers(KeyManager[])}, created by
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
*/
// TODO: Remove in Smack 4.6.
@Deprecated
public B setKeystorePath(String keystorePath) {
this.keystorePath = keystorePath;
return getThis();
@ -988,7 +983,12 @@ public abstract class ConnectionConfiguration {
*
* @param keystoreType the keystore type.
* @return a reference to this builder.
* @deprecated set a key-type aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
* {@link #setKeyManagers(KeyManager[])}, created by
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
*/
// TODO: Remove in Smack 4.6.
@Deprecated
public B setKeystoreType(String keystoreType) {
this.keystoreType = keystoreType;
return getThis();
@ -1000,7 +1000,12 @@ public abstract class ConnectionConfiguration {
*
* @param pkcs11Library the path to the PKCS11 library file.
* @return a reference to this builder.
* @deprecated set a PKCS11-library aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
* {@link #setKeyManagers(KeyManager[])}, created by
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
*/
// TODO: Remove in Smack 4.6.
@Deprecated
public B setPKCS11Library(String pkcs11Library) {
this.pkcs11Library = pkcs11Library;
return getThis();
@ -1276,5 +1281,77 @@ public abstract class ConnectionConfiguration {
public abstract C build();
protected abstract B getThis();
public static KeyManager[] getKeyManagersFrom(String keystoreType, String keystorePath,
CallbackHandler callbackHandler, String pkcs11Library)
throws NoSuchMethodException, SecurityException, ClassNotFoundException, KeyStoreException,
NoSuchProviderException, NoSuchAlgorithmException, CertificateException, IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, UnsupportedCallbackException, UnrecoverableKeyException {
KeyManager[] keyManagers = null;
KeyStore ks = null;
PasswordCallback pcb = null;
if ("PKCS11".equals(keystoreType)) {
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library;
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8));
Provider p = (Provider) c.newInstance(config);
Security.addProvider(p);
ks = KeyStore.getInstance("PKCS11", p);
pcb = new PasswordCallback("PKCS11 Password: ", false);
callbackHandler.handle(new Callback[] { pcb });
ks.load(null, pcb.getPassword());
} else if ("Apple".equals(keystoreType)) {
ks = KeyStore.getInstance("KeychainStore", "Apple");
ks.load(null, null);
// pcb = new PasswordCallback("Apple Keychain",false);
// pcb.setPassword(null);
} else if (keystoreType != null) {
ks = KeyStore.getInstance(keystoreType);
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
pcb = new PasswordCallback("Keystore Password: ", false);
callbackHandler.handle(new Callback[] { pcb });
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
} else {
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
try {
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
char[] password = "changeit".toCharArray();
try {
ks.load(stream, password);
} finally {
CloseableUtil.maybeClose(stream);
}
} catch (IOException e) {
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
ks = KeyStore.getInstance("jks");
// Open the stream again, so that we read it from the beginning.
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
try {
ks.load(stream, null);
} finally {
CloseableUtil.maybeClose(stream);
}
}
}
}
if (ks != null) {
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
if (kmf != null) {
if (pcb == null) {
kmf.init(ks, null);
} else {
kmf.init(ks, pcb.getPassword());
pcb.clearPassword();
}
keyManagers = kmf.getKeyManagers();
}
}
return keyManagers;
}
}
}

View File

@ -170,16 +170,20 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return exception;
}
private boolean callbacksInvoked;
protected final synchronized void maybeInvokeCallbacks() {
if (cancelled) {
if (cancelled || callbacksInvoked) {
return;
}
if ((result != null || exception != null) && completionCallback != null) {
callbacksInvoked = true;
completionCallback.accept(this);
}
if (result != null && successCallback != null) {
callbacksInvoked = true;
AbstractXMPPConnection.asyncGo(new Runnable() {
@Override
public void run() {
@ -188,6 +192,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
});
}
else if (exception != null && exceptionCallback != null) {
callbacksInvoked = true;
AbstractXMPPConnection.asyncGo(new Runnable() {
@Override
public void run() {

View File

@ -221,11 +221,10 @@ public class SmackReactor {
selectWait = 0;
} else {
selectWait = nextScheduledAction.getTimeToDueMillis();
}
if (selectWait < 0) {
// A scheduled action was just released and became ready to execute.
return;
if (selectWait <= 0) {
// A scheduled action was just released and became ready to execute.
return;
}
}
// Before we call select, we handle the pending the interest Ops. This will not block since no other

View File

@ -120,6 +120,16 @@ public abstract class XMPPException extends Exception {
return error;
}
/**
* Gets the stanza associated with this exception.
*
* @return the stanza from which this exception was created or {@code null} if the exception is not from a
* stanza.
*/
public Stanza getStanza() {
return stanza;
}
/**
* Get the request which triggered the error response causing this exception.
*

View File

@ -186,6 +186,10 @@ public class StateDescriptorGraph {
for (GraphVertex<Class<? extends StateDescriptor>> successor : sortedSuccessors) {
GraphVertex<StateDescriptor> successorVertex = successorStateDescriptors.get(successor.element);
if (successorVertex == null) {
// The successor does not exist, probably because its module was not enabled.
continue;
}
node.addOutgoingEdge(successorVertex);
// Recurse further.

View File

@ -202,7 +202,7 @@ public abstract class IQ extends Stanza implements IqView {
// Add the query section if there is one.
IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder(
new IQChildElementXmlStringBuilder(this));
new IQChildElementXmlStringBuilder(getChildElementName(), getChildElementNamespace(), null, xml.getXmlEnvironment()));
// TOOD: Document the cases where iqChildElement is null but childElementName not. And if there are none, change
// the logic.
if (iqChildElement == null) {
@ -399,17 +399,16 @@ public abstract class IQ extends Stanza implements IqView {
private boolean isEmptyElement;
private IQChildElementXmlStringBuilder(IQ iq) {
this(iq.getChildElementName(), iq.getChildElementNamespace());
public IQChildElementXmlStringBuilder(ExtensionElement extensionElement,
XmlEnvironment enclosingXmlEnvironment) {
this(extensionElement.getElementName(), extensionElement.getNamespace(), extensionElement.getLanguage(),
enclosingXmlEnvironment);
}
public IQChildElementXmlStringBuilder(ExtensionElement pe) {
this(pe.getElementName(), pe.getNamespace());
}
private IQChildElementXmlStringBuilder(String element, String namespace) {
prelude(element, namespace);
this.element = element;
private IQChildElementXmlStringBuilder(String elementName, String xmlNs, String xmlLang,
XmlEnvironment enclosingXmlEnvironment) {
super(elementName, xmlNs, xmlLang, enclosingXmlEnvironment);
this.element = elementName;
}
public void setEmptyElement() {

View File

@ -184,6 +184,19 @@ public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements Stanz
return getThis();
}
public final B removeExtension(String elementName, String namespace) {
QName key = new QName(namespace, elementName);
extensionElements.remove(key);
return getThis();
}
public final B removeExtension(ExtensionElement extension) {
QName key = extension.getQName();
List<XmlElement> list = extensionElements.getAll(key);
list.remove(extension);
return getThis();
}
public abstract Stanza build();
public abstract B getThis();

View File

@ -34,7 +34,7 @@ public class ExceptionThrowingCallbackWithHint extends ExceptionThrowingCallback
@Override
public void handleUnparsableStanza(UnparseableStanza packetData) throws IOException {
LOGGER.warning("Parsing exception encountered."
LOGGER.warning("Parsing exception \"" + packetData.getParsingException().getMessage() + "\" encountered."
+ " This exception will be re-thrown, leading to a disconnect."
+ " You can change this behavior by setting a different ParsingExceptionCallback using setParsingExceptionCallback()."
+ " More information an be found in AbstractXMPPConnection's javadoc.");

View File

@ -30,7 +30,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
/**
* <p>
* <b>Deprecation Notice:</b> This class is deprecated, use {@link IQProvider} instead.
* <b>Deprecation Notice:</b> This class is deprecated, use {@link IqProvider} instead.
* </p>
* An abstract class for parsing custom IQ packets. Each IQProvider must be registered with
* the ProviderManager class for it to be used. Every implementation of this

View File

@ -60,4 +60,8 @@ public class SASLAnonymous extends SASLMechanism {
// SASL Anonymous is always successful :)
}
@Override
public boolean requiresPassword() {
return false;
}
}

View File

@ -89,4 +89,11 @@ public class CollectionUtil {
}
return Collections.singletonList(element);
}
public static <T> Set<T> nullSafeUnmodifiableSet(Set<T> set) {
if (set == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(set);
}
}

View File

@ -0,0 +1,29 @@
/**
*
* Copyright 2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
public final class DoOnce {
private boolean done;
public void once(Runnable run) {
if (done) return;
done = true;
run.run();
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2019 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2019-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -76,9 +76,9 @@ public class PacketParserUtils {
return getParserFor(new StringReader(stanza));
}
public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException {
public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException, IOException {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return SmackXmlParser.newXmlParser(inputStreamReader);
return getParserFor(inputStreamReader);
}
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
@ -501,6 +501,23 @@ public class PacketParserUtils {
return parseIQ(parser, null);
}
public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException {
final String id = parser.getAttributeValue("", "id");
IqData iqData = StanzaBuilder.buildIqData(id);
final Jid to = ParserUtils.getJidAttribute(parser, "to");
iqData.to(to);
final Jid from = ParserUtils.getJidAttribute(parser, "from");
iqData.from(from);
String typeString = parser.getAttributeValue("", "type");
final IQ.Type type = IQ.Type.fromString(typeString);
iqData.ofType(type);
return iqData;
}
/**
* Parses an IQ packet.
*
@ -518,18 +535,7 @@ public class PacketParserUtils {
XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
IQ iqPacket = null;
StanzaError error = null;
final String id = parser.getAttributeValue("", "id");
IqData iqData = StanzaBuilder.buildIqData(id);
final Jid to = ParserUtils.getJidAttribute(parser, "to");
iqData.to(to);
final Jid from = ParserUtils.getJidAttribute(parser, "from");
iqData.from(from);
final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
iqData.ofType(type);
IqData iqData = parseIqData(parser);
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
@ -549,8 +555,8 @@ public class PacketParserUtils {
if (provider != null) {
iqPacket = provider.parse(parser, iqData, outerXmlEnvironment);
}
// Note that if we reach this code, it is guranteed that the result IQ contained a child element
// (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_ELEMENT first.
// Note that if we reach this code, it is guaranteed that the result IQ contained a child element
// (RFC 6120 § 8.2.3 6) because otherwise we would have reached the END_ELEMENT first.
else {
// No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
// so that the content of the IQ can be examined later on
@ -571,7 +577,7 @@ public class PacketParserUtils {
}
// Decide what to do when an IQ packet was not understood
if (iqPacket == null) {
switch (type) {
switch (iqData.getType()) {
case error:
// If an IQ packet wasn't created above, create an empty error IQ packet.
iqPacket = new ErrorIQ(error);
@ -585,10 +591,10 @@ public class PacketParserUtils {
}
// Set basic values on the iq packet.
iqPacket.setStanzaId(id);
iqPacket.setTo(to);
iqPacket.setFrom(from);
iqPacket.setType(type);
iqPacket.setStanzaId(iqData.getStanzaId());
iqPacket.setTo(iqData.getTo());
iqPacket.setFrom(iqData.getFrom());
iqPacket.setType(iqData.getType());
iqPacket.setError(error);
return iqPacket;

View File

@ -78,6 +78,7 @@ public class ParserUtils {
throws XmlPullParserException, IOException {
XmlPullParser.Event event = parser.getEventType();
while (!(event == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == depth)) {
assert event != XmlPullParser.Event.END_DOCUMENT;
event = parser.next();
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2020 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2016-2021 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,6 +20,7 @@ package org.jivesoftware.smack.util;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@ -605,4 +606,13 @@ public class StringUtils {
String[] lines = input.split(PORTABLE_NEWLINE_REGEX);
return Arrays.asList(lines);
}
public static List<String> toStrings(Collection<? extends CharSequence> charSequences) {
List<String> res = new ArrayList<>(charSequences.size());
for (CharSequence cs : charSequences) {
String string = cs.toString();
res.add(string);
}
return res;
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2020 Florian Schmaus
* Copyright 2014-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -52,11 +52,13 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
}
public XmlStringBuilder(XmlElement element, XmlEnvironment enclosingXmlEnvironment) {
sb = new LazyStringBuilder();
halfOpenElement(element);
this(element.getElementName(), element.getNamespace(), element.getLanguage(), enclosingXmlEnvironment);
}
public XmlStringBuilder(String elementName, String xmlNs, String xmlLang, XmlEnvironment enclosingXmlEnvironment) {
sb = new LazyStringBuilder();
halfOpenElement(elementName);
String xmlNs = element.getNamespace();
String xmlLang = element.getLanguage();
if (enclosingXmlEnvironment == null) {
xmlnsAttribute(xmlNs);
xmllangAttribute(xmlLang);
@ -286,8 +288,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
public XmlStringBuilder attribute(String name, Enum<?> value) {
assert value != null;
// TODO: Should use toString() instead of name().
attribute(name, value.name());
attribute(name, value.toString());
return this;
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2019-2021 Florian Schmaus
* Copyright 2019-2022 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,9 +28,13 @@ import java.util.function.Predicate;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.AbstractProvider;
import org.jivesoftware.smack.provider.IqProvider;
import org.jivesoftware.smack.provider.Provider;
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.smack.xml.XmlPullParserFactory;
@ -58,41 +62,56 @@ public class SmackTestUtil {
}
}
public static <E extends Element, P extends Provider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
public static <E extends Element, P extends AbstractProvider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(xml, provider, parserKind);
}
public static <E extends Element, P extends Provider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
public static <E extends Element, P extends AbstractProvider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(inputStream, provider, parserKind);
}
public static <E extends Element, P extends Provider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
public static <E extends Element, P extends AbstractProvider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(reader, provider, parserKind);
}
public static <E extends Element> E parse(CharSequence xml, Provider<E> provider, XmlPullParserKind parserKind)
public static <E extends Element> E parse(CharSequence xml, AbstractProvider<E> provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
String xmlString = xml.toString();
Reader reader = new StringReader(xmlString);
return parse(reader, provider, parserKind);
}
public static <E extends Element> E parse(InputStream inputStream, Provider<E> provider, XmlPullParserKind parserKind)
public static <E extends Element> E parse(InputStream inputStream, AbstractProvider<E> provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return parse(inputStreamReader, provider, parserKind);
}
public static <E extends Element> E parse(Reader reader, Provider<E> provider, XmlPullParserKind parserKind)
@SuppressWarnings("unchecked")
public static <E extends Element> E parse(Reader reader, AbstractProvider<E> abstractProvider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
XmlPullParser parser = getParserFor(reader, parserKind);
E element = provider.parse(parser);
final E element;
if (abstractProvider instanceof Provider) {
Provider<E> provider = (Provider<E>) abstractProvider;
element = provider.parse(parser);
} else if (abstractProvider instanceof IqProvider) {
IqData iqData = PacketParserUtils.parseIqData(parser);
parser.next();
ParserUtils.forwardToStartElement(parser);
IqProvider<?> iqProvider = (IqProvider<?>) abstractProvider;
element = (E) iqProvider.parse(parser, iqData);
} else {
throw new AssertionError();
}
return element;
}
@ -132,7 +151,7 @@ public class SmackTestUtil {
}
@SuppressWarnings("unchecked")
private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
private static <E extends Element, P extends AbstractProvider<E>> P providerClassToProvider(Class<P> providerClass) {
P provider;
try {

View File

@ -34,7 +34,13 @@ import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -419,38 +425,11 @@ public class EnhancedDebugger extends SmackDebugger {
// Create a special Reader that wraps the main Reader and logs data to the GUI.
ObservableReader debugReader = new ObservableReader(reader);
readerListener = new ReaderListener() {
@Override
public void read(final String str) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER &&
!EnhancedDebuggerWindow.getInstance().isVisible()) {
// Do not add content if the parent is not visible
return;
}
private final PriorityBlockingQueue<String> buffer = new PriorityBlockingQueue<>();
int index = str.lastIndexOf(">");
if (index != -1) {
if (receivedText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) {
try {
receivedText.replaceRange("", 0, receivedText.getLineEndOffset(0));
}
catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
}
}
receivedText.append(str.substring(0, index + 1));
receivedText.append(NEWLINE);
if (str.length() > index) {
receivedText.append(str.substring(index + 1));
}
}
else {
receivedText.append(str);
}
}
});
@Override
public void read(final String string) {
addBatched(string, buffer, receivedText);
}
};
debugReader.addReaderListener(readerListener);
@ -458,34 +437,11 @@ public class EnhancedDebugger extends SmackDebugger {
// Create a special Writer that wraps the main Writer and logs data to the GUI.
ObservableWriter debugWriter = new ObservableWriter(writer);
writerListener = new WriterListener() {
private final PriorityBlockingQueue<String> buffer = new PriorityBlockingQueue<>();
@Override
public void write(final String str) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER &&
!EnhancedDebuggerWindow.getInstance().isVisible()) {
// Do not add content if the parent is not visible
return;
}
if (sentText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) {
try {
sentText.replaceRange("", 0, sentText.getLineEndOffset(0));
}
catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
}
}
sentText.append(str);
if (str.endsWith(">")) {
sentText.append(NEWLINE);
}
}
});
public void write(final String string) {
addBatched(string, buffer, sentText);
}
};
debugWriter.addWriterListener(writerListener);
@ -497,6 +453,50 @@ public class EnhancedDebugger extends SmackDebugger {
}
private static void addBatched(String string, PriorityBlockingQueue<String> buffer, JTextArea jTextArea) {
buffer.add(string);
SwingUtilities.invokeLater(() -> {
List<String> linesToAdd = new ArrayList<>();
String data;
Instant start = Instant.now();
try {
// To reduce overhead/increase performance, try to process up to a certain amount of lines at the
// same time, when they arrive in rapid succession.
while (linesToAdd.size() < 50
&& Duration.between(start, Instant.now()).compareTo(Duration.ofMillis(100)) < 0
&& (data = buffer.poll(10, TimeUnit.MILLISECONDS)) != null) {
linesToAdd.add(data);
}
} catch (InterruptedException e) {
LOGGER.log(Level.FINER, "Interrupted wait-for-poll in addBatched(). Process all data now.", e);
}
if (linesToAdd.isEmpty()) {
return;
}
if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && !EnhancedDebuggerWindow.getInstance().isVisible()) {
// Do not add content if the parent is not visible
return;
}
// Delete lines from the top, if lines to be added will exceed the maximum.
int linesToDelete = jTextArea.getLineCount() + linesToAdd.size() - EnhancedDebuggerWindow.MAX_TABLE_ROWS;
if (linesToDelete > 0) {
try {
jTextArea.replaceRange("", 0, jTextArea.getLineEndOffset(linesToDelete - 1));
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: "
+ EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
}
}
// Add the new content.
jTextArea.append(String.join(NEWLINE, linesToAdd));
});
}
private void addAdhocPacketPanel() {
// Create UI elements for sending ad-hoc messages.
final JTextArea adhocMessages = new JTextArea();

View File

@ -154,8 +154,7 @@ public final class CarbonManager extends Manager {
// because we also reset in authenticated() if the stream got not resumed, but for maximum correctness,
// also reset here.
enabled_state = false;
boolean removed = connection().removeSyncStanzaListener(carbonsListener);
assert removed;
connection().removeSyncStanzaListener(carbonsListener);
}
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {

View File

@ -0,0 +1,101 @@
/**
*
* Copyright 2022 Micha Kurvers
*
* 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.httpfileupload;
import java.io.IOException;
import java.net.URL;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
/**
* An exception class to provide additional information in case of exceptions during file uploading.
*
*/
public abstract class AbstractHttpUploadException extends IOException {
private static final long serialVersionUID = 1L;
private final long fileSize;
private final Slot slot;
protected AbstractHttpUploadException(long fileSize, Slot slot, String message) {
this(fileSize, slot, message, null);
}
protected AbstractHttpUploadException(long fileSize, Slot slot, String message, Throwable wrappedThrowable) {
super(message, wrappedThrowable);
this.fileSize = fileSize;
this.slot = slot;
}
public long getFileSize() {
return fileSize;
}
public URL getPutUrl() {
return slot.getPutUrl();
}
public Slot getSlot() {
return slot;
}
/**
* Exception thrown when http response returned after upload is not 200.
*/
public static class HttpUploadErrorException extends AbstractHttpUploadException {
private static final long serialVersionUID = 8494356028399474995L;
private final int httpStatus;
private final String responseMsg;
public HttpUploadErrorException(int httpStatus, String responseMsg, long fileSize, Slot slot) {
super(fileSize, slot, "Error response " + httpStatus + " from server during file upload: "
+ responseMsg + ", file size: " + fileSize + ", put URL: "
+ slot.getPutUrl());
this.httpStatus = httpStatus;
this.responseMsg = responseMsg;
}
public int getHttpStatus() {
return httpStatus;
}
public String getResponseMsg() {
return responseMsg;
}
}
/**
* Exception thrown when an unexpected exception occurred during the upload.
*/
public static class HttpUploadIOException extends AbstractHttpUploadException {
private static final long serialVersionUID = 5940866318073349451L;
private final IOException wrappedIOException;
public HttpUploadIOException(long fileSize, Slot slot, IOException cause) {
super(fileSize, slot, "Unexpected error occurred during file upload, file size: " + fileSize
+ ", put Url: " + slot.getPutUrl(), cause);
this.wrappedIOException = cause;
}
public IOException getCausingIOException() {
return this.wrappedIOException;
}
}
}

View File

@ -49,6 +49,8 @@ import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadErrorException;
import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadIOException;
import org.jivesoftware.smackx.httpfileupload.UploadService.Version;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
@ -494,11 +496,12 @@ public final class HttpFileUploadManager extends Manager {
case HttpURLConnection.HTTP_NO_CONTENT:
break;
default:
throw new IOException("Error response " + status + " from server during file upload: "
+ urlConnection.getResponseMessage() + ", file size: " + fileSize + ", put URL: "
+ putUrl);
throw new HttpUploadErrorException(status, urlConnection.getResponseMessage(), fileSize, slot);
}
}
catch (IOException e) {
throw new HttpUploadIOException(fileSize, slot, e);
}
finally {
urlConnection.disconnect();
}

View File

@ -47,15 +47,17 @@ import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.commands.AdHocCommandManager;
import org.jivesoftware.smackx.commands.RemoteCommand;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension;
import org.jivesoftware.smackx.mam.element.MamFinIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.mam.filter.MamResultFilter;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
@ -225,6 +227,8 @@ public final class MamManager extends Manager {
private final AdHocCommandManager adHocCommandManager;
private MamVersion mamVersion = null;
private MamManager(XMPPConnection connection, Jid archiveAddress) {
super(connection);
this.archiveAddress = archiveAddress;
@ -250,6 +254,52 @@ public final class MamManager extends Manager {
return archiveAddress;
}
/**
* Returns the MAM namespace used by this {@link MamManager}. If the archive does not support any MAM namespace
* supported by Smack, null is returned.
*
* @return the MAM namespace used by this manager, null if MAM is not supported
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public String getMamNamespace() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
MamVersion mamVersion = getSupportedMamVersionOrNull();
return mamVersion == null ? null : mamVersion.getNamespace();
}
private MamVersion getSupportedMamVersionOrNull() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
if (mamVersion != null) {
return mamVersion;
}
DiscoverInfo info = serviceDiscoveryManager.discoverInfo(getArchiveAddress());
// Enum values are always returned the order they are declared (see https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3).
// We pick the first version supported by the server.
for (MamVersion v : MamVersion.values()) {
if (info.containsFeature(v.getNamespace())) {
mamVersion = v;
break;
}
}
return mamVersion;
}
private MamVersion getSupportedMamVersionOrThrow() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
MamVersion mamVersion = getSupportedMamVersionOrNull();
if (mamVersion == null) {
throw new UnsupportedOperationException("Message Archive Management is not supported by " + getArchiveAddress());
}
return mamVersion;
}
private MamElementFactory getElementFactory() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
return getSupportedMamVersionOrThrow().newElementFactory();
}
public static final class MamQueryArgs {
private final String node;
@ -275,11 +325,11 @@ public final class MamManager extends Manager {
private DataForm dataForm;
DataForm getDataForm() {
DataForm getDataForm(MamVersion version) {
if (dataForm != null) {
return dataForm;
}
DataForm.Builder dataFormBuilder = getNewMamForm();
DataForm.Builder dataFormBuilder = getNewMamForm(version);
dataFormBuilder.addFields(formFields.values());
dataForm = dataFormBuilder.build();
return dataForm;
@ -472,9 +522,9 @@ public final class MamManager extends Manager {
NotConnectedException, NotLoggedInException, InterruptedException {
String queryId = StringUtils.secureUniqueRandomString();
String node = mamQueryArgs.node;
DataForm dataForm = mamQueryArgs.getDataForm();
DataForm dataForm = mamQueryArgs.getDataForm(mamVersion);
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm);
MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
@ -530,7 +580,7 @@ public final class MamManager extends Manager {
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, NotLoggedInException {
String queryId = StringUtils.secureUniqueRandomString();
MamQueryIQ mamQueryIq = new MamQueryIQ(queryId, node, null);
MamQueryIQ mamQueryIq = getElementFactory().newQueryIQ(queryId, node, null);
mamQueryIq.setTo(archiveAddress);
MamQueryIQ mamResponseQueryIq = connection().sendIqRequestAndWaitForResponse(mamQueryIq);
@ -592,7 +642,7 @@ public final class MamManager extends Manager {
private List<Message> page(RSMSet requestRsmSet) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
String queryId = StringUtils.secureUniqueRandomString();
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, form);
MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, form);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
mamQueryIQ.addExtension(requestRsmSet);
@ -696,9 +746,7 @@ public final class MamManager extends Manager {
* @see <a href="https://xmpp.org/extensions/xep-0313.html#support">XEP-0313 § 7. Determining support</a>
*/
public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// Note that this may return 'null' but SDM's supportsFeature() does the right thing then.
Jid archiveAddress = getArchiveAddress();
return serviceDiscoveryManager.supportsFeature(archiveAddress, MamElements.NAMESPACE);
return getSupportedMamVersionOrNull() != null;
}
public boolean isAdvancedConfigurationSupported() throws InterruptedException, XMPPException, SmackException {
@ -720,8 +768,8 @@ public final class MamManager extends Manager {
throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress);
}
private static DataForm.Builder getNewMamForm() {
FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE);
private static DataForm.Builder getNewMamForm(MamVersion version) {
FormField field = FormField.buildHiddenFormType(version.getNamespace());
DataForm.Builder form = DataForm.builder();
form.addField(field);
return form;
@ -765,7 +813,7 @@ public final class MamManager extends Manager {
*/
public MamPrefsResult retrieveArchivingPreferences() throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
MamPrefsIQ mamPrefIQ = new MamPrefsIQ();
MamPrefsIQ mamPrefIQ = getElementFactory().newPrefsIQ();
return queryMamPrefs(mamPrefIQ);
}
@ -830,6 +878,7 @@ public final class MamManager extends Manager {
public static final class MamPrefs {
private final List<Jid> alwaysJids;
private final List<Jid> neverJids;
private final MamVersion mamVersion;
private DefaultBehavior defaultBehavior;
private MamPrefs(MamPrefsResult mamPrefsResult) {
@ -837,6 +886,7 @@ public final class MamManager extends Manager {
this.alwaysJids = new ArrayList<>(mamPrefsIq.getAlwaysJids());
this.neverJids = new ArrayList<>(mamPrefsIq.getNeverJids());
this.defaultBehavior = mamPrefsIq.getDefault();
this.mamVersion = MamVersion.fromNamespace(mamPrefsIq.getNamespace());
}
public void setDefaultBehavior(DefaultBehavior defaultBehavior) {
@ -856,7 +906,7 @@ public final class MamManager extends Manager {
}
private MamPrefsIQ constructMamPrefsIq() {
return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
return mamVersion.newElementFactory().newPrefsIQ(alwaysJids, neverJids, defaultBehavior);
}
}

View File

@ -0,0 +1,94 @@
/**
*
* Copyright © 2016-2021 Florian Schmaus and Frank Matheron
*
* 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.mam.element;
import java.util.List;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.Jid;
/**
* Factory that creates MAM objects.
*
* @since 4.5.0
*/
public interface MamElementFactory {
/**
* Creates a new {@link MamElementFactory} for the parser based on the namespace of the parser.
* @param parser the XML parser to retrieve the MAM namespace from
* @return the factory suitable for the MAM namespace
*/
static MamElementFactory forParser(XmlPullParser parser) {
String namespace = parser.getNamespace();
return MamVersion.fromNamespace(namespace).newElementFactory();
}
/**
* Create a MAM result extension class.
*
* @param queryId id of the query
* @param id the message's archive UID
* @param forwarded the original message as it was received
* @return the result extension
*/
MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded<Message> forwarded);
/**
* Create a MAM fin IQ class.
*
* @param queryId id of the query
* @param rsmSet the RSM set included in the {@code <fin/>}
* @param complete true if the results returned by the server are complete (no further paging in needed)
* @param stable false if the results returned by the sever are unstable (e.g. they might later change in sequence or content)
* @return the fin IQ
*/
MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable);
/**
* Create a new MAM preferences IQ.
*
* @param alwaysJids JIDs for which all messages are archived by default
* @param neverJids JIDs for which messages are never archived
* @param defaultBehavior default archive behavior
* @return the prefs IQ
*/
MamPrefsIQ newPrefsIQ(List<Jid> alwaysJids, List<Jid> neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior);
/**
* Construct a new MAM {@code <prefs/>} IQ retrieval request (IQ type 'get').
*
* @return the prefs IQ
*/
MamPrefsIQ newPrefsIQ();
/**
* Create a new MAM Query IQ.
*
* @param queryId id of the query
* @param node pubsub node id when querying a pubsub node, null when not querying a pubsub node
* @param dataForm the dataform containing the query parameters
* @return the query IQ
*/
MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm);
}

View File

@ -18,15 +18,13 @@ package org.jivesoftware.smackx.mam.element;
import java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageView;
import org.jivesoftware.smack.packet.XmlElement;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jxmpp.jid.Jid;
@ -41,8 +39,6 @@ import org.jxmpp.jid.Jid;
*/
public class MamElements {
public static final String NAMESPACE = "urn:xmpp:mam:2";
/**
* MAM result extension class.
*
@ -50,18 +46,13 @@ public class MamElements {
* Archive Management</a>
*
*/
public static class MamResultExtension implements ExtensionElement {
public abstract static class MamResultExtension implements ExtensionElement {
/**
* result element.
*/
public static final String ELEMENT = "result";
/**
* The qualified name of the MAM result extension element.
*/
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
/**
* id of the result.
*/
@ -77,20 +68,27 @@ public class MamElements {
*/
private String queryId;
protected final MamVersion version;
/**
* MAM result extension constructor.
*
* @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param id TODO javadoc me please
* @param forwarded TODO javadoc me please
*/
public MamResultExtension(String queryId, String id, Forwarded<Message> forwarded) {
public MamResultExtension(MamVersion version, String queryId, String id, Forwarded<Message> forwarded) {
if (StringUtils.isEmpty(id)) {
throw new IllegalArgumentException("id must not be null or empty");
}
if (forwarded == null) {
throw new IllegalArgumentException("forwarded must no be null");
}
if (version == null) {
throw new IllegalArgumentException("version must not be null");
}
this.version = version;
this.id = id;
this.forwarded = forwarded;
this.queryId = queryId;
@ -130,7 +128,7 @@ public class MamElements {
@Override
public final String getNamespace() {
return NAMESPACE;
return version.getNamespace();
}
@Override
@ -148,7 +146,13 @@ public class MamElements {
}
public static MamResultExtension from(MessageView message) {
return message.getExtension(MamResultExtension.class);
for (XmlElement extension : message.getExtensions()) {
if (extension instanceof MamResultExtension) {
return (MamResultExtension) extension;
}
}
return null;
}
}

View File

@ -35,11 +35,6 @@ public class MamFinIQ extends IQ {
*/
public static final String ELEMENT = "fin";
/**
* the IQ NAMESPACE.
*/
public static final String NAMESPACE = MamElements.NAMESPACE;
/**
* RSM set.
*/
@ -63,13 +58,14 @@ public class MamFinIQ extends IQ {
/**
* MamFinIQ constructor.
*
* @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param rsmSet TODO javadoc me please
* @param complete TODO javadoc me please
* @param stable TODO javadoc me please
*/
public MamFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
super(ELEMENT, NAMESPACE);
public MamFinIQ(MamVersion version, String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
super(ELEMENT, version.getNamespace());
if (rsmSet == null) {
throw new IllegalArgumentException("rsmSet must not be null");
}

View File

@ -46,11 +46,6 @@ public class MamPrefsIQ extends IQ {
*/
public static final String ELEMENT = "prefs";
/**
* the IQ NAMESPACE.
*/
public static final String NAMESPACE = MamElements.NAMESPACE;
/**
* list of always.
*/
@ -68,9 +63,11 @@ public class MamPrefsIQ extends IQ {
/**
* Construct a new MAM {@code <prefs/>} IQ retrieval request (IQ type 'get').
*
* @param version TODO javadoc me please *
*/
public MamPrefsIQ() {
super(ELEMENT, NAMESPACE);
public MamPrefsIQ(MamVersion version) {
super(ELEMENT, version.getNamespace());
alwaysJids = null;
neverJids = null;
defaultBehavior = null;
@ -79,12 +76,13 @@ public class MamPrefsIQ extends IQ {
/**
* MAM preferences IQ constructor.
*
* @param version TODO javadoc me please
* @param alwaysJids TODO javadoc me please
* @param neverJids TODO javadoc me please
* @param defaultBehavior TODO javadoc me please
*/
public MamPrefsIQ(List<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior) {
super(ELEMENT, NAMESPACE);
public MamPrefsIQ(MamVersion version, List<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior) {
super(ELEMENT, version.getNamespace());
setType(Type.set);
this.alwaysJids = alwaysJids;
this.neverJids = neverJids;

View File

@ -35,11 +35,6 @@ public class MamQueryIQ extends IQ {
*/
public static final String ELEMENT = QUERY_ELEMENT;
/**
* the MAM query IQ NAMESPACE.
*/
public static final String NAMESPACE = MamElements.NAMESPACE;
private final String queryId;
private final String node;
private final DataForm dataForm;
@ -47,41 +42,45 @@ public class MamQueryIQ extends IQ {
/**
* MAM query IQ constructor.
*
* @param version TODO javadoc me please
* @param queryId TODO javadoc me please
*/
public MamQueryIQ(String queryId) {
this(queryId, null, null);
public MamQueryIQ(MamVersion version, String queryId) {
this(version, queryId, null, null);
setType(IQ.Type.get);
}
/**
* MAM query IQ constructor.
*
* @param version TODO javadoc me please
* @param form TODO javadoc me please
*/
public MamQueryIQ(DataForm form) {
this(null, null, form);
public MamQueryIQ(MamVersion version, DataForm form) {
this(version, null, null, form);
}
/**
* MAM query IQ constructor.
*
* @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param form TODO javadoc me please
*/
public MamQueryIQ(String queryId, DataForm form) {
this(queryId, null, form);
public MamQueryIQ(MamVersion version, String queryId, DataForm form) {
this(version, queryId, null, form);
}
/**
* MAM query IQ constructor.
*
* @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param node TODO javadoc me please
* @param dataForm TODO javadoc me please
*/
public MamQueryIQ(String queryId, String node, DataForm dataForm) {
super(ELEMENT, NAMESPACE);
public MamQueryIQ(MamVersion version, String queryId, String node, DataForm dataForm) {
super(ELEMENT, version.getNamespace());
this.queryId = queryId;
this.node = node;
this.dataForm = dataForm;
@ -91,9 +90,9 @@ public class MamQueryIQ extends IQ {
if (formType == null) {
throw new IllegalArgumentException("If a data form is given it must posses a hidden form type field");
}
if (!formType.equals(MamElements.NAMESPACE)) {
if (!formType.equals(version.getNamespace())) {
throw new IllegalArgumentException(
"Value of the hidden form type field must be '" + MamElements.NAMESPACE + "'");
"Value of the hidden form type field must be '" + version.getNamespace() + "'");
}
addExtension(dataForm);
}

View File

@ -0,0 +1,66 @@
/**
*
* Copyright © 2016-2021 Florian Schmaus and Frank Matheron
*
* 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.mam.element;
import java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.Jid;
class MamV1ElementFactory implements MamElementFactory {
@Override
public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded<Message> forwarded) {
return new MamV1ResultExtension(queryId, id, forwarded);
}
@Override
public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
return new MamFinIQ(MamVersion.MAM1, queryId, rsmSet, complete, stable);
}
@Override
public MamPrefsIQ newPrefsIQ(List<Jid> alwaysJids, List<Jid> neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) {
return new MamPrefsIQ(MamVersion.MAM1, alwaysJids, neverJids, defaultBehavior);
}
@Override
public MamPrefsIQ newPrefsIQ() {
return new MamPrefsIQ(MamVersion.MAM1);
}
@Override
public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) {
return new MamQueryIQ(MamVersion.MAM1, queryId, node, dataForm);
}
public static class MamV1ResultExtension extends MamElements.MamResultExtension {
/**
* The qualified name of the MAM result extension element.
*/
public static final QName QNAME = new QName(MamVersion.MAM1.getNamespace(), ELEMENT);
MamV1ResultExtension(String queryId, String id, Forwarded<Message> forwarded) {
super(MamVersion.MAM1, queryId, id, forwarded);
}
}
}

View File

@ -0,0 +1,66 @@
/**
*
* Copyright © 2016-2021 Florian Schmaus and Frank Matheron
*
* 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.mam.element;
import java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.Jid;
class MamV2ElementFactory implements MamElementFactory {
@Override
public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded<Message> forwarded) {
return new MamV2ResultExtension(queryId, id, forwarded);
}
@Override
public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
return new MamFinIQ(MamVersion.MAM2, queryId, rsmSet, complete, stable);
}
@Override
public MamPrefsIQ newPrefsIQ(List<Jid> alwaysJids, List<Jid> neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) {
return new MamPrefsIQ(MamVersion.MAM2, alwaysJids, neverJids, defaultBehavior);
}
@Override
public MamPrefsIQ newPrefsIQ() {
return new MamPrefsIQ(MamVersion.MAM2);
}
@Override
public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) {
return new MamQueryIQ(MamVersion.MAM2, queryId, node, dataForm);
}
public static class MamV2ResultExtension extends MamElements.MamResultExtension {
/**
* The qualified name of the MAM result extension element.
*/
public static final QName QNAME = new QName(MamVersion.MAM2.getNamespace(), ELEMENT);
MamV2ResultExtension(String queryId, String id, Forwarded<Message> forwarded) {
super(MamVersion.MAM2, queryId, id, forwarded);
}
}
}

View File

@ -0,0 +1,69 @@
/**
*
* Copyright © 2016-2021 Florian Schmaus and Frank Matheron
*
* 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.mam.element;
/**
* MAM versions supported by Smack.
*
* @since 4.5.0
*/
public enum MamVersion {
// Note that the order in which the enum values are defined, is also the order in which we attempt to find a
// supported version. The versions should therefore be listed in order of newest to oldest, so that Smack prefers
// using a newer version over an older version.
MAM2("urn:xmpp:mam:2") {
@Override
public MamElementFactory newElementFactory() {
return new MamV2ElementFactory();
}
},
MAM1("urn:xmpp:mam:1") {
@Override
public MamElementFactory newElementFactory() {
return new MamV1ElementFactory();
}
};
private final String namespace;
MamVersion(String namespace) {
this.namespace = namespace;
}
/**
* Each MAM version is identified by its namespace. Returns the namespace for this MAM version.
* @return the namespace of the MAM version
*/
public String getNamespace() {
return namespace;
}
/**
* Creates a new factory that creates IQ's and extension objects for this MAM version.
* @return the factory
*/
public abstract MamElementFactory newElementFactory();
public static MamVersion fromNamespace(String namespace) {
for (MamVersion v : MamVersion.values()) {
if (v.namespace.equals(namespace)) {
return v;
}
}
throw new IllegalArgumentException("Unsupported namespace: " + namespace);
}
}

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamFinIQ;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.rsm.provider.RSMSetProvider;
@ -41,6 +42,7 @@ public class MamFinIQProvider extends IQProvider<MamFinIQ> {
@Override
public MamFinIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
MamElementFactory elementFactory = MamElementFactory.forParser(parser);
String queryId = parser.getAttributeValue("", "queryid");
boolean complete = ParserUtils.getBooleanAttribute(parser, "complete", false);
boolean stable = ParserUtils.getBooleanAttribute(parser, "stable", true);
@ -65,7 +67,7 @@ public class MamFinIQProvider extends IQProvider<MamFinIQ> {
}
}
return new MamFinIQ(queryId, rsmSet, complete, stable);
return elementFactory.newFinIQ(queryId, rsmSet, complete, stable);
}
}

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
@ -41,19 +42,17 @@ import org.jxmpp.jid.impl.JidCreate;
*/
public class MamPrefsIQProvider extends IQProvider<MamPrefsIQ> {
public static final MamPrefsIQProvider INSTANCE = new MamPrefsIQProvider();
@Override
public MamPrefsIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
String iqType = parser.getAttributeValue("", "type");
MamElementFactory elementFactory = MamElementFactory.forParser(parser);
String defaultBehaviorString = parser.getAttributeValue("", "default");
DefaultBehavior defaultBehavior = null;
if (defaultBehaviorString != null) {
defaultBehavior = DefaultBehavior.valueOf(defaultBehaviorString);
}
if (iqType == null) {
iqType = "result";
}
List<Jid> alwaysJids = null;
List<Jid> neverJids = null;
@ -82,7 +81,7 @@ public class MamPrefsIQProvider extends IQProvider<MamPrefsIQ> {
}
}
return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
return elementFactory.newPrefsIQ(alwaysJids, neverJids, defaultBehavior);
}
private static List<Jid> iterateJids(XmlPullParser parser) throws XmlPullParserException, IOException {

View File

@ -24,6 +24,7 @@ import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.xdata.provider.DataFormProvider;
@ -41,6 +42,7 @@ public class MamQueryIQProvider extends IQProvider<MamQueryIQ> {
@Override
public MamQueryIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
MamElementFactory elementFactory = MamElementFactory.forParser(parser);
DataForm dataForm = null;
String queryId = parser.getAttributeValue("", "queryid");
String node = parser.getAttributeValue("", "node");
@ -68,7 +70,7 @@ public class MamQueryIQProvider extends IQProvider<MamQueryIQ> {
}
}
return new MamQueryIQ(queryId, node, dataForm);
return elementFactory.newQueryIQ(queryId, node, dataForm);
}
}

View File

@ -28,6 +28,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.forward.provider.ForwardedProvider;
import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension;
/**
@ -43,6 +44,7 @@ public class MamResultProvider extends ExtensionElementProvider<MamResultExtensi
@Override
public MamResultExtension parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException, ParseException {
MamElementFactory elementFactory = MamElementFactory.forParser(parser);
Forwarded<Message> forwarded = null;
String queryId = parser.getAttributeValue("", "queryid");
String id = parser.getAttributeValue("", "id");
@ -69,7 +71,7 @@ public class MamResultProvider extends ExtensionElementProvider<MamResultExtensi
}
}
return new MamResultExtension(queryId, id, forwarded);
return elementFactory.newResultExtension(queryId, id, forwarded);
}
}

View File

@ -35,6 +35,26 @@
<namespace>urn:xmpp:mam:2</namespace>
<className>org.jivesoftware.smackx.mam.provider.MamResultProvider</className>
</extensionProvider>
<iqProvider>
<elementName>prefs</elementName>
<namespace>urn:xmpp:mam:1</namespace>
<className>org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider</className>
</iqProvider>
<iqProvider>
<elementName>query</elementName>
<namespace>urn:xmpp:mam:1</namespace>
<className>org.jivesoftware.smackx.mam.provider.MamQueryIQProvider</className>
</iqProvider>
<iqProvider>
<elementName>fin</elementName>
<namespace>urn:xmpp:mam:1</namespace>
<className>org.jivesoftware.smackx.mam.provider.MamFinIQProvider</className>
</iqProvider>
<extensionProvider>
<elementName>result</elementName>
<namespace>urn:xmpp:mam:1</namespace>
<className>org.jivesoftware.smackx.mam.provider.MamResultProvider</className>
</extensionProvider>
<!-- XEP-0323: Internet of Things - Data -->
<iqProvider>

View File

@ -23,7 +23,7 @@ import java.util.Date;
import java.util.List;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@ -35,7 +35,7 @@ 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'>" + "<value>"
+ MamElements.NAMESPACE + "</value>" + "</field>";
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>";
for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) {
xml += "<field var='" + fieldsNames.get(i) + "'>" + "<value>" + fieldsValues.get(i) + "</value>"
@ -51,7 +51,7 @@ public class FiltersTest extends MamTest {
Date date = new Date();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsSince(date).build();
DataForm dataForm = mamQueryArgs.getDataForm();
DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List<String> fields = new ArrayList<>();
fields.add("start");
@ -66,7 +66,7 @@ public class FiltersTest extends MamTest {
Date date = new Date();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsBefore(date).build();
DataForm dataForm = mamQueryArgs.getDataForm();
DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List<String> fields = new ArrayList<>();
fields.add("end");
@ -81,7 +81,7 @@ public class FiltersTest extends MamTest {
Jid jid = JidTestUtil.BARE_JID_1;
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsToJid(jid).build();
DataForm dataForm = mamQueryArgs.getDataForm();
DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List<String> fields = new ArrayList<>();
fields.add("with");

View File

@ -19,54 +19,62 @@ package org.jivesoftware.smackx.mam;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.List;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.test.util.SmackTestUtil.XmlPullParserKind;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.jxmpp.jid.Jid;
public class MamPrefIQProviderTest extends MamTest {
private static final String exampleMamPrefsIQ1 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
private static final String exampleMamPrefsIQ1 = "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "</always>" + "<never>"
+ "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>" + "</iq>";
+ "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>";
private static final String exampleMamPrefsIQ2 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
private static final String exampleMamPrefsIQ2 = "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "<jid>montague@montague.lit</jid>" + "</always>"
+ "<never>" + "</never>" + "</prefs>" + "</iq>";
+ "<never>" + "</never>" + "</prefs>";
private static final String exampleMamPrefsIQ3 = "<iq type='get' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2'>" + "</prefs>"
+ "</iq>";
private static final String exampleMamPrefsIQ3 = "<prefs xmlns='urn:xmpp:mam:2'>" + "</prefs>";
private static final String exampleMamPrefsResultIQ = "<iq type='result' id='juliet3'>"
+ "<prefs xmlns='urn:xmpp:mam:2' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>"
+ "</always>" + "<never>" + "<jid>sarasa@montague.lit</jid>" + "<jid>montague@montague.lit</jid>"
+ "</never>" + "</prefs>" + "</iq>";
@Test
public void checkMamPrefsIQProvider() throws Exception {
XmlPullParser parser1 = PacketParserUtils.getParserFor(exampleMamPrefsIQ1);
MamPrefsIQ mamPrefIQ1 = new MamPrefsIQProvider().parse(parser1);
@ParameterizedTest
@EnumSource(value = SmackTestUtil.XmlPullParserKind.class)
public void checkMamPrefsIQProvider(XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
XmlPullParser parser1 = SmackTestUtil.getParserFor(exampleMamPrefsIQ1, parserKind);
MamPrefsIQ mamPrefIQ1 = MamPrefsIQProvider.INSTANCE.parse(parser1);
assertEquals(IQ.Type.set, mamPrefIQ1.getType());
assertEquals(mamPrefIQ1.getAlwaysJids().get(0).toString(), "romeo@montague.lit");
assertEquals(mamPrefIQ1.getNeverJids().get(0).toString(), "montague@montague.lit");
XmlPullParser parser2 = PacketParserUtils.getParserFor(exampleMamPrefsIQ2);
MamPrefsIQ mamPrefIQ2 = new MamPrefsIQProvider().parse(parser2);
XmlPullParser parser2 = SmackTestUtil.getParserFor(exampleMamPrefsIQ2, parserKind);
MamPrefsIQ mamPrefIQ2 = MamPrefsIQProvider.INSTANCE.parse(parser2);
assertEquals(IQ.Type.set, mamPrefIQ2.getType());
assertEquals(mamPrefIQ2.getAlwaysJids().get(0).toString(), "romeo@montague.lit");
assertEquals(mamPrefIQ2.getAlwaysJids().get(1).toString(), "montague@montague.lit");
assertTrue(mamPrefIQ2.getNeverJids().isEmpty());
XmlPullParser parser3 = PacketParserUtils.getParserFor(exampleMamPrefsIQ3);
MamPrefsIQ mamPrefIQ3 = new MamPrefsIQProvider().parse(parser3);
XmlPullParser parser3 = SmackTestUtil.getParserFor(exampleMamPrefsIQ3, parserKind);
MamPrefsIQ mamPrefIQ3 = MamPrefsIQProvider.INSTANCE.parse(parser3);
assertEquals(IQ.Type.set, mamPrefIQ3.getType());
}

View File

@ -23,6 +23,7 @@ import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.BeforeAll;
@ -47,9 +48,9 @@ public class MamTest extends SmackTestSuite {
protected DataForm getNewMamForm() throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm");
Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm", MamVersion.class);
methodGetNewMamForm.setAccessible(true);
DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager);
DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager, MamVersion.MAM2);
return dataFormBuilder.build();
}

View File

@ -22,6 +22,7 @@ import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -40,7 +41,7 @@ public class PagingTest extends MamTest {
int max = 10;
RSMSet rsmSet = new RSMSet(max);
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setStanzaId("sarasa");
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.addExtension(rsmSet);

View File

@ -23,9 +23,9 @@ import java.util.List;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.Jid;
@ -33,16 +33,16 @@ import org.jxmpp.jid.impl.JidCreate;
public class PreferencesTest {
private static final String retrievePrefsStanzaExample = "<iq id='sarasa' type='get'>" + "<prefs xmlns='" + MamElements.NAMESPACE
private static final String retrievePrefsStanzaExample = "<iq id='sarasa' type='get'>" + "<prefs xmlns='" + MamVersion.MAM2.getNamespace()
+ "'/>" + "</iq>";
private static final String updatePrefsStanzaExample = "<iq id='sarasa' type='set'>" + "<prefs xmlns='" + MamElements.NAMESPACE
private static final String updatePrefsStanzaExample = "<iq id='sarasa' type='set'>" + "<prefs xmlns='" + MamVersion.MAM2.getNamespace()
+ "' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>" + "<jid>other@montague.lit</jid>"
+ "</always>" + "<never>" + "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>" + "</iq>";
@Test
public void checkRetrievePrefsStanza() throws Exception {
MamPrefsIQ mamPrefIQ = new MamPrefsIQ();
MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ();
mamPrefIQ.setStanzaId("sarasa");
assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), retrievePrefsStanzaExample);
}
@ -56,7 +56,7 @@ public class PreferencesTest {
List<Jid> neverJids = new ArrayList<>();
neverJids.add(JidCreate.from("montague@montague.lit"));
MamPrefsIQ mamPrefIQ = new MamPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster);
MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster);
mamPrefIQ.setStanzaId("sarasa");
assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), updatePrefsStanzaExample);
}

View File

@ -29,9 +29,9 @@ import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@ -41,7 +41,7 @@ public class QueryArchiveTest extends MamTest {
private static final String mamSimpleQueryIQ = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
+ "<result xmlns='urn:xmpp:mam:2' queryid='g27' id='34482-21985-73620'>"
@ -54,7 +54,7 @@ public class QueryArchiveTest extends MamTest {
@Test
public void checkMamQueryIQ() throws Exception {
DataForm dataForm = getNewMamForm();
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setStanzaId("sarasa");
assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), mamSimpleQueryIQ);
@ -80,7 +80,7 @@ public class QueryArchiveTest extends MamTest {
Forwarded<Message> forwarded = new Forwarded<>(forwardedMessage, delay);
message.addExtension(new MamResultExtension("g27", "34482-21985-73620", forwarded));
message.addExtension(MamVersion.MAM2.newElementFactory().newResultExtension("g27", "34482-21985-73620", forwarded));
assertEquals(mamQueryResultExample, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());

View File

@ -22,8 +22,8 @@ import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@ -32,13 +32,13 @@ public class ResultsLimitTest extends MamTest {
private static final String resultsLimitStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
@Test
public void checkResultsLimit() throws Exception {
DataForm dataForm = getNewMamForm();
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setStanzaId("sarasa");

View File

@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -32,18 +32,18 @@ import org.jxmpp.jid.JidTestUtil;
public class RetrieveFormFieldsTest extends MamTest {
private static final String retrieveFormFieldStanza = "<iq id='sarasa' type='get'>" + "<query xmlns='" + MamElements.NAMESPACE
private static final String retrieveFormFieldStanza = "<iq id='sarasa' type='get'>" + "<query xmlns='" + MamVersion.MAM2.getNamespace()
+ "' queryid='testid'></query>" + "</iq>";
private static final String additionalFieldsStanza = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>"
+ "<value>" + MamElements.NAMESPACE + "</value>" + "</field>"
+ "<value>" + MamVersion.MAM2.getNamespace() + "</value>" + "</field>"
+ "<field var='urn:example:xmpp:free-text-search'>" + "<value>Hi</value>" + "</field>"
+ "<field var='urn:example:xmpp:stanza-content'>" + "<value>one@exampleone.org</value>" + "</field>"
+ "</x>";
@Test
public void checkRetrieveFormFieldsStanza() throws Exception {
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId);
MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId);
mamQueryIQ.setStanzaId("sarasa");
assertEquals(retrieveFormFieldStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
@ -63,7 +63,7 @@ public class RetrieveFormFieldsTest extends MamTest {
.withAdditionalFormField(field1)
.withAdditionalFormField(field2)
.build();
DataForm dataForm = mamQueryArgs.getDataForm();
DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
String dataFormResult = dataForm.toXML().toString();

View File

@ -153,7 +153,7 @@ public class DataPacketExtension implements ExtensionElement {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this));
XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace));
xml.closeElement(this);
return xml;
}

View File

@ -615,9 +615,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
if (annouceLocalStreamHost) {
// add local proxy on first position if exists
List<StreamHost> localProxies = getLocalStreamHost();
if (localProxies != null) {
streamHosts.addAll(localProxies);
}
streamHosts.addAll(localProxies);
}
// query SOCKS5 proxies for network settings
@ -652,12 +650,14 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
/**
* Returns the stream host information of the local SOCKS5 proxy containing the IP address and
* the port or null if local SOCKS5 proxy is not running.
* the port. The returned list may be empty if the local SOCKS5 proxy is not running.
*
* @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy
* is not running
* @return the stream host information of the local SOCKS5 proxy
*/
public List<StreamHost> getLocalStreamHost() {
// Ensure that the local SOCKS5 proxy is running (if enabled).
Socks5Proxy.getSocks5Proxy();
List<StreamHost> streamHosts = new ArrayList<>();
XMPPConnection connection = connection();

View File

@ -700,7 +700,7 @@ public final class EntityCapsManager extends Manager {
for (FormField f : fs) {
sb.append(f.getFieldName());
sb.append('<');
formFieldValuesToCaps(f.getRawValues(), sb);
formFieldValuesToCaps(f.getRawValueCharSequences(), sb);
}
}

View File

@ -921,6 +921,10 @@ public final class ServiceDiscoveryManager extends Manager {
return entityCapabilitiesChangedListeners.add(entityCapabilitiesChangedListener);
}
public boolean removeEntityCapabilitiesChangedListener(EntityCapabilitiesChangedListener entityCapabilitiesChangedListener) {
return entityCapabilitiesChangedListeners.remove(entityCapabilitiesChangedListener);
}
private static final int RENEW_ENTITY_CAPS_DELAY_MILLIS = 25;
private ScheduledAction renewEntityCapsScheduledAction;

View File

@ -258,6 +258,14 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView {
return features.contains(new Feature(feature));
}
public static boolean nullSafeContainsFeature(DiscoverInfo discoverInfo, CharSequence feature) {
if (discoverInfo == null) {
return false;
}
return discoverInfo.containsFeature(feature);
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.optAttribute("node", getNode());

View File

@ -68,6 +68,12 @@ public class FormFieldRegistry {
}
}
public static void register(String formType, FormField.Type fieldType, String... fieldNames) {
for (String fieldName : fieldNames) {
register(formType, fieldName, fieldType);
}
}
public static void register(String formType, String fieldName, FormField.Type fieldType) {
StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided");
Objects.requireNonNull(fieldType);

View File

@ -53,7 +53,7 @@ public class JingleUtil {
JingleContentDescription description,
JingleContentTransport transport) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_initiate)
.setSessionId(sessionId)
.setInitiator(connection.getUser());
@ -118,7 +118,7 @@ public class JingleUtil {
JingleContentDescription description,
JingleContentTransport transport) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setResponder(connection.getUser())
.setAction(JingleAction.session_accept)
.setSessionId(sessionId);
@ -153,7 +153,7 @@ public class JingleUtil {
}
public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_terminate)
.setSessionId(sessionId)
.setReason(reason);
@ -232,7 +232,7 @@ public class JingleUtil {
public Jingle createSessionTerminateContentCancel(FullJid recipient, String sessionId,
JingleContent.Creator contentCreator, String contentName) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_terminate)
.setSessionId(sessionId);
@ -314,7 +314,7 @@ public class JingleUtil {
}
public Jingle createSessionPing(FullJid recipient, String sessionId) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setSessionId(sessionId)
.setAction(JingleAction.session_info);
@ -343,7 +343,7 @@ public class JingleUtil {
public Jingle createTransportReplace(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setInitiator(initiator)
.setSessionId(sessionId)
.setAction(JingleAction.transport_replace);
@ -370,7 +370,7 @@ public class JingleUtil {
public Jingle createTransportAccept(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.transport_accept)
.setInitiator(initiator)
.setSessionId(sessionId);
@ -397,7 +397,7 @@ public class JingleUtil {
public Jingle createTransportReject(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.transport_reject)
.setInitiator(initiator)
.setSessionId(sessionId);

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2014-2017 Florian Schmaus
* Copyright 2003-2007 Jive Software, 2014-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,7 +21,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqBuilder;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
@ -65,9 +69,9 @@ public final class Jingle extends IQ {
private final List<JingleContent> contents;
private Jingle(String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason,
private Jingle(Builder builder, String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason,
List<JingleContent> contents) {
super(ELEMENT, NAMESPACE);
super(builder, ELEMENT, NAMESPACE);
this.sessionId = StringUtils.requireNotNullNorEmpty(sessionId, "Jingle session ID must not be null");
this.action = Objects.requireNonNull(action, "Jingle action must not be null");
this.initiator = initiator;
@ -169,11 +173,31 @@ public final class Jingle extends IQ {
return xml;
}
/**
* Deprecated, do not use.
*
* @return a builder.
* @deprecated use {@link #builder(XMPPConnection)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.6.
public static Builder getBuilder() {
return new Builder();
return builder(StandardStanzaIdSource.DEFAULT.getNewStanzaId());
}
public static final class Builder {
public static Builder builder(XMPPConnection connection) {
return new Builder(connection);
}
public static Builder builder(IqData iqData) {
return new Builder(iqData);
}
public static Builder builder(String stanzaId) {
return new Builder(stanzaId);
}
public static final class Builder extends IqBuilder<Builder, Jingle> {
private String sid;
private JingleAction action;
@ -186,7 +210,16 @@ public final class Jingle extends IQ {
private List<JingleContent> contents;
private Builder() {
Builder(IqData iqCommon) {
super(iqCommon);
}
Builder(XMPPConnection connection) {
super(connection);
}
Builder(String stanzaId) {
super(stanzaId);
}
public Builder setSessionId(String sessionId) {
@ -228,8 +261,14 @@ public final class Jingle extends IQ {
return this;
}
@Override
public Jingle build() {
return new Jingle(sid, action, initiator, responder, reason, contents);
return new Jingle(this, sid, action, initiator, responder, reason, contents);
}
@Override
public Builder getThis() {
return this;
}
}
}

View File

@ -145,6 +145,11 @@ public final class JingleContent implements XmlElement {
xml.optAttribute(DISPOSITION_ATTRIBUTE_NAME, disposition);
xml.attribute(NAME_ATTRIBUTE_NAME, name);
xml.optAttribute(SENDERS_ATTRIBUTE_NAME, senders);
if (description == null && transport == null) {
return xml.closeEmptyElement();
}
xml.rightAngleBracket();
xml.optAppend(description);

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2021 Florian Schmaus
* Copyright 2017-2022 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,7 @@ public class JingleReason implements XmlElement {
public static final String ELEMENT = "reason";
public static final String NAMESPACE = Jingle.NAMESPACE;
public static final String TEXT_ELEMENT = "text";
public static AlternativeSession AlternativeSession(String sessionId) {
return new AlternativeSession(sessionId);
@ -105,9 +106,17 @@ public class JingleReason implements XmlElement {
}
protected final Reason reason;
private final String text;
private final XmlElement element;
public JingleReason(Reason reason) {
this(reason, null, null);
}
public JingleReason(Reason reason, String text, XmlElement element) {
this.reason = reason;
this.text = text;
this.element = element;
}
@Override
@ -120,12 +129,34 @@ public class JingleReason implements XmlElement {
return NAMESPACE;
}
/**
* An optional text that provides human-readable information about the reason for the action.
*
* @return a human-readable text with information regarding this reason or <code>null</code>.
* @since 4.4.5
*/
public String getText() {
return text;
}
/**
* An optional element that provides more detailed machine-readable information about the reason for the action.
*
* @return an element with machine-readable information about this reason or <code>null</code>.
* @since 4.4.5
*/
public XmlElement getElement() {
return element;
}
@Override
public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment);
xml.rightAngleBracket();
xml.emptyElement(reason);
xml.optElement(TEXT_ELEMENT, text);
xml.optAppend(element);
xml.closeElement(this);
return xml;
@ -142,7 +173,11 @@ public class JingleReason implements XmlElement {
private final String sessionId;
public AlternativeSession(String sessionId) {
super(Reason.alternative_session);
this(sessionId, null, null);
}
public AlternativeSession(String sessionId, String text, XmlElement element) {
super(Reason.alternative_session, text, element);
if (StringUtils.isNullOrEmpty(sessionId)) {
throw new NullPointerException("SessionID must not be null or empty.");
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2019 Florian Schmaus
* Copyright 2017-2022 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,11 +19,14 @@ package org.jivesoftware.smackx.jingle.provider;
import java.io.IOException;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.StandardExtensionElement;
import org.jivesoftware.smack.packet.XmlElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.IqProvider;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -40,13 +43,13 @@ import org.jivesoftware.smackx.jingle.element.UnknownJingleContentTransport;
import org.jxmpp.jid.FullJid;
public class JingleProvider extends IQProvider<Jingle> {
public class JingleProvider extends IqProvider<Jingle> {
private static final Logger LOGGER = Logger.getLogger(JingleProvider.class.getName());
@Override
public Jingle parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
Jingle.Builder builder = Jingle.getBuilder();
public Jingle parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
Jingle.Builder builder = Jingle.builder(iqData);
String actionString = parser.getAttributeValue("", Jingle.ACTION_ATTRIBUTE_NAME);
if (actionString != null) {
@ -75,16 +78,7 @@ public class JingleProvider extends IQProvider<Jingle> {
builder.addJingleContent(content);
break;
case JingleReason.ELEMENT:
parser.next();
String reasonString = parser.getName();
JingleReason reason;
if (reasonString.equals("alternative-session")) {
parser.next();
String sid = parser.nextText();
reason = new JingleReason.AlternativeSession(sid);
} else {
reason = new JingleReason(Reason.fromString(reasonString));
}
JingleReason reason = parseJingleReason(parser);
builder.setReason(reason);
break;
default:
@ -177,4 +171,57 @@ public class JingleProvider extends IQProvider<Jingle> {
return builder.build();
}
public static JingleReason parseJingleReason(XmlPullParser parser)
throws XmlPullParserException, IOException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
final String jingleNamespace = parser.getNamespace();
JingleReason.Reason reason = null;
XmlElement element = null;
String text = null;
// 'sid' is only set if the reason is 'alternative-session'.
String sid = null;
outerloop: while (true) {
XmlPullParser.TagEvent event = parser.nextTag();
switch (event) {
case START_ELEMENT:
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (namespace.equals(jingleNamespace)) {
switch (elementName) {
case "text":
text = parser.nextText();
break;
case "alternative-session":
parser.next();
sid = parser.nextText();
break;
default:
reason = Reason.fromString(elementName);
break;
}
} else {
element = PacketParserUtils.parseExtensionElement(elementName, namespace, parser, null);
}
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
JingleReason res;
if (sid != null) {
res = new JingleReason.AlternativeSession(sid, text, element);
} else {
res = new JingleReason(reason, text, element);
}
return res;
}
}

View File

@ -36,6 +36,10 @@ public abstract class JingleTransportManager<D extends JingleContentTransport> i
}
public XMPPConnection getConnection() {
return connection();
}
public XMPPConnection connection() {
return connection;
}

View File

@ -148,7 +148,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders,
JingleContent.Creator contentCreator, String contentName, String streamId,
String candidateId) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection());
jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
JingleContent.Builder cb = JingleContent.getBuilder();
@ -165,7 +165,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
}
public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection());
jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
JingleContent.Builder cb = JingleContent.getBuilder();
@ -184,7 +184,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId,
JingleContent.Senders senders, JingleContent.Creator creator,
String name, String streamId) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection());
jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator);
JingleContent.Builder cb = JingleContent.getBuilder();
@ -202,7 +202,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId,
JingleContent.Senders senders, JingleContent.Creator creator,
String name, String streamId, String candidateId) {
Jingle.Builder jb = Jingle.getBuilder();
Jingle.Builder jb = Jingle.builder(connection());
jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info);
JingleContent.Builder cb = JingleContent.getBuilder();

View File

@ -26,6 +26,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.util.DoOnce;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -35,6 +36,8 @@ import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
public class JivePropertiesExtensionProvider extends ExtensionElementProvider<JivePropertiesExtension> {
private static final DoOnce LOG_OBJECT_NOT_ENABLED = new DoOnce();
private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtensionProvider.class.getName());
/**
@ -113,7 +116,10 @@ public class JivePropertiesExtensionProvider extends ExtensionElementProvider<Ji
}
}
else {
LOGGER.severe("JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)");
LOG_OBJECT_NOT_ENABLED.once(
() -> LOGGER.severe(
"JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)")
);
}
}
if (name != null && value != null) {

View File

@ -78,6 +78,17 @@ public class MucConfigFormManager {
*/
public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret";
/**
* The constant String {@value}.
*/
public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom";
/**
* The constant String {@value}.
*/
public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom";
private final MultiUserChat multiUserChat;
private final FillableForm answerForm;
private final List<Jid> owners;
@ -151,6 +162,15 @@ public class MucConfigFormManager {
return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY);
}
/**
* Check if the room supports being moderated in the configuration.
*
* @return <code>true</code> if supported, <code>false</code> if not.
*/
public boolean supportsModeration() {
return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM);
}
/**
* Make the room for members only.
*
@ -176,6 +196,68 @@ public class MucConfigFormManager {
return this;
}
/**
* Make the room moderated.
*
* @return a reference to this object.
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException {
return setModerated(true);
}
/**
* Set if the room is members only. Rooms are not members only per default.
*
* @param isModerated if the room should be moderated.
* @return a reference to this object.
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException {
if (!supportsModeration()) {
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM);
}
answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated);
return this;
}
/**
* Make the room publicly searchable.
*
* @return a reference to this object.
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException {
return setPublic(true);
}
/**
* Make the room hidden (not publicly searchable).
*
* @return a reference to this object.
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException {
return setPublic(false);
}
/**
* Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service).
*
* @param isPublic if the room should be publicly searchable.
* @return a reference to this object.
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException {
if (!supportsModeration()) {
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
}
answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic);
return this;
}
/**
* Check if the room supports password protection.
*

View File

@ -205,12 +205,20 @@ public class MultiUserChat {
if (from == null) {
return;
}
final EntityFullJid myRoomJID = myRoomJid;
final EntityFullJid myRoomJID = getMyRoomJid();
final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
final MUCUser mucUser = MUCUser.from(packet);
switch (presence.getType()) {
case available:
if (!processedReflectedSelfPresence
&& mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) {
processedReflectedSelfPresence = true;
synchronized (this) {
notify();
}
}
Presence oldPresence = occupantsMap.put(from, presence);
if (oldPresence != null) {
// Get the previous occupant's affiliation & role
@ -228,11 +236,6 @@ public class MultiUserChat {
newAffiliation,
isUserStatusModification,
from);
} else if (mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) {
processedReflectedSelfPresence = true;
synchronized (this) {
notify();
}
} else {
// A new occupant has joined the room
for (ParticipantStatusListener listener : participantStatusListeners) {
@ -259,23 +262,24 @@ 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);
}
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());
}
for (UserStatusListener listener : userStatusListeners) {
listener.roomDestroyed(alternateMuc, destroy.getReason());
}
}
if (isUserStatusModification) {
for (UserStatusListener listener : userStatusListeners) {
listener.removed(mucUser, presence);
@ -728,7 +732,7 @@ public class MultiUserChat {
* @return true if currently in the multi user chat room.
*/
public boolean isJoined() {
return myRoomJid != null;
return getMyRoomJid() != null;
}
/**
@ -764,7 +768,7 @@ 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.
final EntityFullJid myRoomJid = this.myRoomJid;
final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
throw new MucNotJoinedException(this);
}
@ -793,11 +797,14 @@ public class MultiUserChat {
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(reflectedLeavePresenceFilters);
// 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();
Presence reflectedLeavePresence;
try {
reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
} finally {
// Reset occupant information after we send the leave presence. This ensures that we only call userHasLeft()
// and reset the local MUC state after we successfully left the MUC (or if an exception occurred).
userHasLeft();
}
return reflectedLeavePresence;
}
@ -1193,13 +1200,23 @@ public class MultiUserChat {
* @return the nickname currently being used.
*/
public Resourcepart getNickname() {
final EntityFullJid myRoomJid = this.myRoomJid;
final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
return null;
}
return myRoomJid.getResourcepart();
}
/**
* Return the full JID of the user in the room, or <code>null</code> if the room is not joined.
*
* @return the full JID of the user in the room, or <code>null</code>.
* @since 4.5.0
*/
public EntityFullJid getMyRoomJid() {
return myRoomJid;
}
/**
* Changes the occupant's nickname to a new nickname within the room. Each room occupant
* will receive two presence packets. One of type "unavailable" for the old nickname and one
@ -1256,7 +1273,7 @@ public class MultiUserChat {
* @throws MucNotJoinedException if not joined to the Multi-User Chat.
*/
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException {
final EntityFullJid myRoomJid = this.myRoomJid;
final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
throw new MucNotJoinedException(this);
}
@ -2579,7 +2596,7 @@ public class MultiUserChat {
}
public boolean serviceSupportsStableIds() {
return mucServiceDiscoInfo.containsFeature(MultiUserChatConstants.STABLE_ID_FEATURE);
return DiscoverInfo.nullSafeContainsFeature(mucServiceDiscoInfo, MultiUserChatConstants.STABLE_ID_FEATURE);
}
@Override

View File

@ -38,11 +38,7 @@ public class FormNode extends NodeExtension {
* @param submitForm The form
*/
public FormNode(FormNodeType formType, DataForm submitForm) {
super(formType.getNodeElement());
if (submitForm == null)
throw new IllegalArgumentException("Submit form cannot be null");
configForm = submitForm;
this(formType, null, submitForm);
}
/**
@ -55,9 +51,6 @@ public class FormNode extends NodeExtension {
*/
public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) {
super(formType.getNodeElement(), nodeId);
if (submitForm == null)
throw new IllegalArgumentException("Submit form cannot be null");
configForm = submitForm;
}

View File

@ -35,6 +35,11 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
public class FormNodeProvider extends EmbeddedExtensionProvider<FormNode> {
@Override
protected FormNode createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends XmlElement> content) {
return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), (DataForm) content.iterator().next());
DataForm dataForm = null;
if (!content.isEmpty()) {
dataForm = (DataForm) content.get(0);
}
return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), dataForm);
}
}

View File

@ -50,24 +50,25 @@ public class ItemProvider extends ExtensionElementProvider<Item> {
String xmlns = parser.getNamespace();
ItemNamespace itemNamespace = ItemNamespace.fromXmlns(xmlns);
XmlPullParser.Event tag = parser.next();
XmlPullParser.TagEvent event = parser.nextTag();
switch (event) {
case START_ELEMENT:
String payloadElemName = parser.getName();
String payloadNS = parser.getNamespace();
if (tag == XmlPullParser.Event.END_ELEMENT) {
return new Item(itemNamespace, id, node);
}
else {
String payloadElemName = parser.getName();
String payloadNS = parser.getNamespace();
final ExtensionElementProvider<ExtensionElement> extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS);
if (extensionProvider == null) {
// TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together.
CharSequence payloadText = PacketParserUtils.parseElement(parser, true);
return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString()));
}
else {
return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser));
}
final ExtensionElementProvider<ExtensionElement> extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS);
if (extensionProvider == null) {
// TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together.
CharSequence payloadText = PacketParserUtils.parseElement(parser, true);
return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString()));
}
else {
return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser));
}
case END_ELEMENT:
return new Item(itemNamespace, id, node);
default:
throw new AssertionError("unknown: " + event);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Aditya Borikar
* Copyright 2020 Aditya Borikar, 2021 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,7 +20,7 @@ import java.util.List;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smackx.formtypes.FormFieldRegistry;
import org.jivesoftware.smackx.mediaelement.element.MediaElement;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.FormFieldChildElement;
@ -47,6 +47,11 @@ public final class SoftwareInfoForm extends FilledForm {
public static final String SOFTWARE_VERSION = "software_version";
public static final String ICON = "icon";
static {
FormFieldRegistry.register(FORM_TYPE, FormField.Type.text_single,
OS, OS_VERSION, SOFTWARE, SOFTWARE_VERSION);
}
private SoftwareInfoForm(DataForm dataForm) {
super(dataForm);
}

View File

@ -27,35 +27,26 @@ import org.jxmpp.util.XmppDateTime;
public class AbstractMultiFormField extends FormField {
private final List<String> values;
private final List<String> rawValues;
private final List<Value> values;
protected AbstractMultiFormField(Builder<?, ?> builder) {
super(builder);
values = CollectionUtil.cloneAndSeal(builder.values);
rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
}
@Override
public final List<String> getValues() {
public final List<Value> getRawValues() {
return values;
}
@Override
public final List<String> getRawValues() {
return rawValues;
}
public abstract static class Builder<F extends AbstractMultiFormField, B extends FormField.Builder<F, B>>
extends FormField.Builder<F, B> {
private List<String> values;
private List<String> rawValues;
private List<Value> values;
protected Builder(AbstractMultiFormField formField) {
super(formField);
values = CollectionUtil.newListWith(formField.getValues());
values = CollectionUtil.newListWith(formField.getRawValues());
}
protected Builder(String fieldName, FormField.Type type) {
@ -65,7 +56,6 @@ public class AbstractMultiFormField extends FormField {
private void ensureValuesAreInitialized() {
if (values == null) {
values = new ArrayList<>();
rawValues = new ArrayList<>();
}
}
@ -77,11 +67,13 @@ public class AbstractMultiFormField extends FormField {
public abstract B addValue(CharSequence value);
public B addValueVerbatim(CharSequence value) {
return addValueVerbatim(new Value(value));
}
public B addValueVerbatim(Value value) {
ensureValuesAreInitialized();
String valueString = value.toString();
values.add(valueString);
rawValues.add(valueString);
values.add(value);
return getThis();
}

View File

@ -74,8 +74,15 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField {
return setValue(value);
}
public B setValue(Value value) {
this.value = value.getValue().toString();
this.rawValue = value;
return getThis();
}
public B setValue(CharSequence value) {
this.rawValue = this.value = value.toString();
this.value = value.toString();
rawValue = new Value(this.value);
return getThis();
}

View File

@ -35,7 +35,31 @@ public class BooleanFormField extends SingleValueFormField {
return value.toString();
}
public Boolean getValueAsBoolean() {
/**
* Get the value of the booelan field. Note that, if no explicit boolean value is provided, in the form of "true",
* "false", "0", or "1", then the default value of a boolean field is <code>false</code>, according to
* XEP-0004 § 3.3.
*
* @return the boolean value of this form field.
*/
public boolean getValueAsBoolean() {
if (value == null) {
return false;
}
return value;
}
/**
* Get the value of the boolean field or maybe <code>null</code>. Note that you usually want to use
* {@link #getValueAsBoolean()} instead of this method, as {@link #getValueAsBoolean()} considers the default value
* of boolean fields. That is, boolean form fields have the value <code>false</code> if not explicitly set to
* something else.
*
* @return the boolean value of this form field or <code>null</code> if no value was explicitly provided.
* @see #getValueAsBoolean()
* @since 4.4.5
*/
public Boolean getValueAsBooleanOrNull() {
return value;
}
@ -71,17 +95,21 @@ public class BooleanFormField extends SingleValueFormField {
@Deprecated
// TODO: Remove in Smack 4.6.
public Builder addValue(CharSequence value) {
return setValue(value);
return setValue(new Value(value));
}
public Builder setValue(CharSequence value) {
rawValue = value.toString();
boolean valueBoolean = ParserUtils.parseXmlBoolean(rawValue);
return setValue(valueBoolean);
return setValue(new Value(value));
}
public Builder setValue(Value value) {
this.value = ParserUtils.parseXmlBoolean(value.getValue().toString());
rawValue = value;
return getThis();
}
public Builder setValue(boolean value) {
rawValue = Boolean.toString(value);
rawValue = new Value(Boolean.toString(value));
this.value = value;
return this;
}

View File

@ -33,6 +33,7 @@ import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -269,9 +270,25 @@ public abstract class FormField implements XmlElement {
*
* @return a List of the default values or answered values of the question.
*/
public abstract List<? extends CharSequence> getValues();
public List<? extends CharSequence> getValues() {
return getRawValueCharSequences();
}
public abstract List<String> getRawValues();
public abstract List<Value> getRawValues();
private transient List<CharSequence> rawValueCharSequences;
public final List<CharSequence> getRawValueCharSequences() {
if (rawValueCharSequences == null) {
List<Value> rawValues = getRawValues();
rawValueCharSequences = new ArrayList<>(rawValues.size());
for (Value value : rawValues) {
rawValueCharSequences.add(value.value);
}
}
return rawValueCharSequences;
}
public boolean hasValueSet() {
List<?> values = getValues();
@ -385,12 +402,15 @@ public abstract class FormField implements XmlElement {
protected transient List<XmlElement> extraXmlChildElements;
/**
* Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses.
*/
protected void populateExtraXmlChildElements() {
List<? extends CharSequence> values = getValues();
List<Value> values = getRawValues();
// Note that we need to create a new ArrayList here, since subclasses may add to it by overriding
// populateExtraXmlChildElements.
extraXmlChildElements = new ArrayList<>(values.size());
for (CharSequence value : values) {
extraXmlChildElements.add(new Value(value));
}
extraXmlChildElements.addAll(values);
}
@Override
@ -414,7 +434,8 @@ public abstract class FormField implements XmlElement {
populateExtraXmlChildElements();
}
if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) {
if (formFieldChildElements.isEmpty()
&& (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) {
buf.closeEmptyElement();
} else {
buf.rightAngleBracket();
@ -580,7 +601,7 @@ public abstract class FormField implements XmlElement {
* @return a reference to this builder.
*/
public B setLabel(String label) {
this.label = StringUtils.requireNotNullNorEmpty(label, "label must not be null or empty");
this.label = Objects.requireNonNull(label, "label must not be null");
return getThis();
}

View File

@ -23,13 +23,14 @@ import java.util.List;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.util.JidUtil;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public final class JidMultiFormField extends FormField {
private final List<Jid> values;
private final List<String> rawValues;
private final List<Value> rawValues;
JidMultiFormField(Builder builder) {
super(builder);
@ -43,7 +44,7 @@ public final class JidMultiFormField extends FormField {
}
@Override
public List<String> getRawValues() {
public List<Value> getRawValues() {
return rawValues;
}
@ -54,7 +55,7 @@ public final class JidMultiFormField extends FormField {
public static final class Builder extends FormField.Builder<JidMultiFormField, JidMultiFormField.Builder> {
private List<Jid> values;
private List<String> rawValues;
private List<Value> rawValues;
private Builder(JidMultiFormField jidMultiFormField) {
super(jidMultiFormField);
@ -79,27 +80,29 @@ public final class JidMultiFormField extends FormField {
}
public Builder addValue(Jid jid) {
return addValue(jid, null);
}
public Builder addValue(Jid jid, String rawValue) {
if (rawValue == null) {
rawValue = jid.toString();
}
Value value = new Value(jid);
ensureValuesAreInitialized();
values.add(jid);
rawValues.add(rawValue);
rawValues.add(value);
return getThis();
}
public Builder addValue(Value value) throws XmppStringprepException {
Jid jid = JidCreate.from(value.getValue());
ensureValuesAreInitialized();
values.add(jid);
rawValues.add(value);
return this;
}
public Builder addValues(Collection<? extends Jid> jids) {
ensureValuesAreInitialized();
values.addAll(jids);
rawValues.addAll(JidUtil.toStringList(jids));
for (Jid jid : jids) {
addValue(jid);
}
return this;
}

View File

@ -17,6 +17,8 @@
package org.jivesoftware.smackx.xdata;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class JidSingleFormField extends SingleValueFormField {
@ -60,11 +62,13 @@ public class JidSingleFormField extends SingleValueFormField {
public Builder setValue(Jid value, String rawValue) {
this.value = value;
if (rawValue != null) {
this.rawValue = rawValue;
} else {
this.rawValue = value.toString();
}
this.rawValue = new Value(value);
return getThis();
}
public Builder setValue(Value value) throws XmppStringprepException {
this.value = JidCreate.from(value.getValue());
this.rawValue = value;
return this;
}

View File

@ -23,7 +23,7 @@ import org.jivesoftware.smack.util.CollectionUtil;
public abstract class SingleValueFormField extends FormField {
private final String rawValue;
private final Value rawValue;
protected SingleValueFormField(Builder<?, ?> builder) {
super(builder);
@ -38,24 +38,23 @@ public abstract class SingleValueFormField extends FormField {
public abstract CharSequence getValue();
public final String getRawValue() {
public final Value getRawValue() {
return rawValue;
}
@Override
public final List<String> getRawValues() {
String rawValue = getRawValue();
public final List<Value> getRawValues() {
Value rawValue = getRawValue();
return CollectionUtil.emptyOrSingletonListFrom(rawValue);
}
@Override
protected void populateExtraXmlChildElements() {
CharSequence value = getValue();
if (value == null) {
if (rawValue == null) {
return;
}
extraXmlChildElements = Collections.singletonList(new Value(value));
extraXmlChildElements = Collections.singletonList(rawValue);
}
public abstract static class Builder<F extends SingleValueFormField, B extends Builder<F, B>>
@ -65,11 +64,12 @@ public abstract class SingleValueFormField extends FormField {
super(fieldName, type);
}
protected Builder(FormField formField) {
protected Builder(SingleValueFormField formField) {
super(formField);
rawValue = formField.getRawValue();
}
protected String rawValue;
protected Value rawValue;
@Override
protected void resetInternal() {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.xdata.AbstractMultiFormField;
import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField;
import org.jivesoftware.smackx.xdata.BooleanFormField;
@ -54,7 +56,8 @@ public interface FormReader {
return Collections.emptyList();
}
AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class);
return multiFormField.getValues();
List<? extends CharSequence> charSequences = multiFormField.getValues();
return StringUtils.toStrings(charSequences);
}
default Boolean readBoolean(String fieldName) {

View File

@ -49,9 +49,6 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.xdatalayout.packet.DataLayout;
import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
/**
* The DataFormProvider parses DataForm packets.
*
@ -237,9 +234,7 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
case jid_multi:
JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName);
for (FormField.Value value : values) {
String rawValue = value.getValue().toString();
Jid jid = JidCreate.from(rawValue);
jidMultiBuilder.addValue(jid, rawValue);
jidMultiBuilder.addValue(value);
}
builder = jidMultiBuilder;
break;
@ -247,9 +242,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
ensureAtMostSingleValue(type, values);
JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName);
if (!values.isEmpty()) {
String rawValue = values.get(0).getValue().toString();
Jid jid = JidCreate.from(rawValue);
jidSingleBuilder.setValue(jid, rawValue);
FormField.Value value = values.get(0);
jidSingleBuilder.setValue(value);
}
builder = jidSingleBuilder;
break;
@ -303,7 +297,7 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName);
ensureAtMostSingleValue(builder.getType(), values);
if (values.size() == 1) {
String value = values.get(0).getValue().toString();
FormField.Value value = values.get(0);
builder.setValue(value);
}
return builder;

View File

@ -20,5 +20,6 @@
<className>org.jivesoftware.smackx.receipts.DeliveryReceiptManager</className>
<className>org.jivesoftware.smackx.iqversion.VersionManager</className>
<className>org.jivesoftware.smackx.caps.EntityCapsManager</className>
<className>org.jivesoftware.smackx.softwareinfo.form.SoftwareInfoForm</className>
</startupClasses>
</smack>

View File

@ -67,7 +67,6 @@ public class AMPExtensionTest {
AMPExtensionProvider ampProvider = new AMPExtensionProvider();
XmlPullParser parser = PacketParserUtils.getParserFor(INCORRECT_RECEIVING_STANZA_STREAM);
assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next());
assertEquals(AMPExtension.ELEMENT, parser.getName());
ExtensionElement extension = ampProvider.parse(parser);
@ -85,7 +84,6 @@ public class AMPExtensionTest {
AMPExtensionProvider ampProvider = new AMPExtensionProvider();
XmlPullParser parser = PacketParserUtils.getParserFor(CORRECT_SENDING_STANZA_STREAM);
assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next());
assertEquals(AMPExtension.ELEMENT, parser.getName());
ExtensionElement extension = ampProvider.parse(parser);
assertTrue(extension instanceof AMPExtension);

View File

@ -75,8 +75,7 @@ public class JingleContentTest extends SmackTestSuite {
assertEquals(content1.toXML().toString(), builder.build().toXML().toString());
String xml =
"<content xmlns='urn:xmpp:jingle:1' creator='initiator' disposition='session' name='A name' senders='both'>" +
"</content>";
"<content xmlns='urn:xmpp:jingle:1' creator='initiator' disposition='session' name='A name' senders='both'/>";
assertEquals(xml, content1.toXML().toString());
}
}

View File

@ -38,7 +38,7 @@ public class JingleTest extends SmackTestSuite {
@Test
public void emptyBuilderTest() {
Jingle.Builder builder = Jingle.getBuilder();
Jingle.Builder builder = Jingle.builder("id");
assertThrows(IllegalArgumentException.class, () -> {
builder.build();
});
@ -48,7 +48,7 @@ public class JingleTest extends SmackTestSuite {
public void onlySessionIdBuilderTest() {
String sessionId = "testSessionId";
Jingle.Builder builder = Jingle.getBuilder();
Jingle.Builder builder = Jingle.builder("id");
builder.setSessionId(sessionId);
assertThrows(IllegalArgumentException.class, () -> {
builder.build();
@ -59,7 +59,7 @@ public class JingleTest extends SmackTestSuite {
public void parserTest() throws XmppStringprepException {
String sessionId = "testSessionId";
Jingle.Builder builder = Jingle.getBuilder();
Jingle.Builder builder = Jingle.builder("id");
builder.setSessionId(sessionId);
builder.setAction(JingleAction.session_initiate);

View File

@ -0,0 +1,48 @@
/**
*
* Copyright 2021 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.jingle.element;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.StreamOpen;
import org.junit.jupiter.api.Test;
public class JingleTest {
@Test
public void noRedundantNamespaceTest() {
Jingle.Builder jingleBuilder = Jingle.builder("test-id");
jingleBuilder.setSessionId("MySession");
jingleBuilder.setAction(JingleAction.content_accept);
JingleContent.Builder jingleContentBuilder = JingleContent.getBuilder();
jingleContentBuilder.setName("Hello world");
jingleContentBuilder.setCreator(JingleContent.Creator.initiator);
jingleBuilder.addJingleContent(jingleContentBuilder.build());
Jingle iq = jingleBuilder.build();
String actualXml = iq.toXML(StreamOpen.CLIENT_NAMESPACE).toString();
String expectedXml
= "<iq id='test-id' type='set'>"
+ "<jingle xmlns='urn:xmpp:jingle:1' action='content-accept' sid='MySession'>"
+ "<content creator='initiator' name='Hello world'/>"
+ "</jingle></iq>";
assertEquals(expectedXml, actualXml);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus
* Copyright 2017-2022 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,23 +17,30 @@
package org.jivesoftware.smackx.jingle.provider;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.packet.StandardExtensionElement;
import org.jivesoftware.smack.packet.XmlElement;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.jingle.element.Jingle;
import org.jivesoftware.smackx.jingle.element.JingleContentDescription;
import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
import org.jivesoftware.smackx.jingle.element.JingleReason;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
public class JingleProviderTest {
@Test
public void testParseUnknownJingleContentDescrption() throws Exception {
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void testParseUnknownJingleContentDescrption(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
final String unknownJingleContentDescriptionNamespace = "urn:xmpp:jingle:unknown-description:5";
final String unknownJingleContentDescription =
// @formatter:off
@ -50,8 +57,8 @@ public class JingleProviderTest {
"</file>" +
"</description>";
// @formatter:on
XmlPullParser parser = createTestJingle(unknownJingleContentDescription);
Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser);
CharSequence xml = createTestJingle(unknownJingleContentDescription);
Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
JingleContentDescription jingleContentDescription = jingle.getSoleContentOrThrow().getDescription();
@ -59,8 +66,10 @@ public class JingleProviderTest {
assertEquals(unknownJingleContentDescriptionNamespace, parsedUnknownJingleContentDescriptionNamespace);
}
@Test
public void testParseUnknownJingleContentTransport() throws Exception {
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void testParseUnknownJingleContentTransport(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
final String unknownJingleContentTransportNamespace = "urn:xmpp:jingle:unknown-transport:foo:1";
final String unknownJingleContentTransport =
// @formatter:off
@ -81,8 +90,8 @@ public class JingleProviderTest {
" type='direct'/>" +
"</transport>";
// @formatter:on
XmlPullParser parser = createTestJingle(unknownJingleContentTransport);
Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser);
CharSequence xml = createTestJingle(unknownJingleContentTransport);
Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
JingleContentTransport jingleContentTransport = jingle.getSoleContentOrThrow().getTransport();
@ -90,7 +99,38 @@ public class JingleProviderTest {
assertEquals(unknownJingleContentTransportNamespace, parsedUnknownJingleContentTransportNamespace);
}
private static XmlPullParser createTestJingle(String... childs) throws XmlPullParserException, IOException {
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void testReasonElementWithExtraElement(SmackTestUtil.XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
String xml = "<iq from='juliet@capulet.lit/balcony'"
+ " id='le71fa63'"
+ " to='romeo@montague.lit/orchard'"
+ " type='set'>"
+ "<jingle xmlns='urn:xmpp:jingle:1'"
+ " action='session-terminate'"
+ " sid='a73sjjvkla37jfea'>"
+ "<reason>"
+ "<success/>"
+ "<my-element xmlns='https://example.org' foo='bar'/>"
+ "</reason>"
+ "</jingle>"
+ "</iq>";
Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
JingleReason jingleReason = jingle.getReason();
assertEquals(JingleReason.Reason.success, jingleReason.asEnum());
XmlElement element = jingleReason.getElement();
// TODO: Use JUnit 5.8's assertInstanceOf when possible
// assertInstanceOf(StandardExtesionElement.class, extraElement);
assertTrue(element instanceof StandardExtensionElement);
StandardExtensionElement extraElement = (StandardExtensionElement) element;
assertEquals("https://example.org", extraElement.getNamespace());
assertEquals("bar", extraElement.getAttributes().get("foo"));
}
private static CharSequence createTestJingle(String... childs) throws XmlPullParserException, IOException {
StringBuilder sb = new StringBuilder();
sb.append(// @formatter:off
"<iq from='romeo@montague.example/dr4hcr0st3lup4c'" +
@ -98,9 +138,9 @@ public class JingleProviderTest {
" to='juliet@capulet.example/yn0cl4bnw0yr3vym'" +
" type='set'>" +
"<jingle xmlns='urn:xmpp:jingle:1' " +
" action='session-initiate' " +
" initiator='romeo@montague.example/dr4hcr0st3lup4c' " +
" sid='851ba2'>" +
"action='session-initiate' " +
"initiator='romeo@montague.example/dr4hcr0st3lup4c' " +
"sid='851ba2'>" +
"<content creator='initiator' name='a-file-offer' senders='initiator'>"
// @formatter:on
);
@ -114,9 +154,6 @@ public class JingleProviderTest {
// @formatter:on
);
String jingleStanza = sb.toString();
XmlPullParser parser = PacketParserUtils.getParserFor(jingleStanza);
return parser;
return sb;
}
}

View File

@ -0,0 +1,48 @@
/**
*
* Copyright 2021 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
public class ItemProviderTest {
/**
* Check that {@link ItemProvider} is able to parse items which have whitespace before their Payload.
*
* @param parserKind the used parser Kind
* @throws XmlPullParserException if an XML pull parser exception occurs.
* @throws IOException if an IO exception occurs.
* @throws SmackParsingException if an Smack parsing exception occurs.
* @see <a href="https://igniterealtime.atlassian.net/jira/software/c/projects/SMACK/issues/SMACK-918">SMACK-918</a>
*/
@ParameterizedTest
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
public void whitespaceBeforeItemPayload(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException {
String item = "<item id='13a3710c-68c3-4da2-8484-d8d9c77af91e' xmlns='http://jabber.org/protocol/pubsub#event'>"
+ "\n <geoloc xmlns='http://jabber.org/protocol/geoloc' xml:lang='en'/>"
+ "</item>";
SmackTestUtil.parse(item, ItemProvider.class, parserKind);
}
}

View File

@ -17,6 +17,8 @@
package org.jivesoftware.smackx.xdata;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.JidTestUtil;
@ -35,4 +37,18 @@ class FormFieldTest {
assertXmlSimilar(expectedXml, xml);
}
@Test
public void testEmptyLabel() {
TextSingleFormField.Builder builder = FormField.textSingleBuilder("type");
builder.setLabel("");
TextSingleFormField formField = builder.build();
assertEquals("", formField.getLabel());
}
@Test
public void testThrowExceptionWhenNullLabel() {
TextSingleFormField.Builder builder = FormField.textSingleBuilder("type");
assertThrows(IllegalArgumentException.class, () -> builder.setLabel(null));
}
}

View File

@ -115,4 +115,33 @@ public class DataFormProviderTest {
assertEquals(2, items.size());
}
@Test
public void testRetrieveFieldWithEmptyLabel() throws XmlPullParserException, IOException, SmackParsingException {
String form =
"<x xmlns='jabber:x:data' type='form'>" +
" <title>Advanced User Search</title>" +
" <instructions>The following fields are available for searching. Wildcard (*) characters are allowed as part of the query.</instructions>" +
" <field var='FORM_TYPE' label='' type='hidden'>" +
" <value>jabber:iq:search</value>" +
" </field>" +
" <field label='Search' var='search'>" +
" <required/>" +
" </field>" +
" <field label='Username' var='Username' type='boolean'>" +
" <value>true</value>" +
" </field>" +
" <field label='Name' var='Name' type='boolean'>" +
" <value>true</value>" +
" </field>" +
" <field label='Email' var='Email' type='boolean'>" +
" <value>true</value>" +
" </field>" +
"</x>";
XmlPullParser parser = PacketParserUtils.getParserFor(form);
DataForm dataForm = DataFormProvider.INSTANCE.parse(parser);
FormField usernameFormField = dataForm.getField("FORM_TYPE");
assertEquals(FormField.Type.hidden, usernameFormField.getType());
assertEquals("", usernameFormField.getLabel());
}
}

View File

@ -39,7 +39,6 @@ public class XHTMLExtensionProviderTest {
public void parsesWell() throws IOException, XmlPullParserException {
InputStream inputStream = getClass().getResourceAsStream(XHTML_EXTENSION_SAMPLE_RESOURCE_NAME);
XmlPullParser parser = PacketParserUtils.getParserFor(inputStream);
parser.next();
XHTMLExtensionProvider provider = new XHTMLExtensionProvider();
ExtensionElement extension = provider.parse(parser, parser.getDepth(), null);

View File

@ -469,11 +469,14 @@ public final class Roster extends Manager {
@Override
public void processException(Exception exception) {
rosterState = RosterState.uninitialized;
Level logLevel;
Level logLevel = Level.SEVERE;
if (exception instanceof NotConnectedException) {
logLevel = Level.FINE;
} else {
logLevel = Level.SEVERE;
} else if (exception instanceof XMPPErrorException) {
Condition condition = ((XMPPErrorException) exception).getStanzaError().getCondition();
if (condition == Condition.feature_not_implemented || condition == Condition.service_unavailable) {
logLevel = Level.FINE;
}
}
LOGGER.log(logLevel, "Exception reloading roster", exception);
for (RosterLoadedListener listener : rosterLoadedListeners) {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2020 Florian Schmaus
* Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -85,6 +85,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
* @param action the action to perform.
* @throws Exception in case of an exception.
*/
@SuppressWarnings("ThreadPriorityCheck")
protected void performActionAndWaitForPresence(XMPPConnection conA, XMPPConnection conB, ThrowingRunnable action)
throws Exception {
final SimpleResultSyncPoint presenceReceivedSyncPoint = new SimpleResultSyncPoint();
@ -109,5 +110,8 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
} finally {
conA.removeAsyncStanzaListener(presenceListener);
}
// TODO: Ugly hack to make tests using this method more reliable. Ideally no test would use this method.
Thread.yield();
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2020 Florian Schmaus
* Copyright 2015-2021 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,8 +19,11 @@ package org.igniterealtime.smack.inttest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -33,6 +36,7 @@ import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Function;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ParserUtils;
@ -101,8 +105,12 @@ public final class Configuration {
public final Set<String> enabledTests;
private final Map<String, Set<String>> enabledTestsMap;
public final Set<String> disabledTests;
private final Map<String, Set<String>> disabledTestsMap;
public final String defaultConnectionNickname;
public final Set<String> enabledConnections;
@ -117,6 +125,13 @@ public final class Configuration {
public final DnsResolver dnsResolver;
public enum CompatibilityMode {
standardsCompliant,
ejabberd,
}
public final CompatibilityMode compatibilityMode;
private Configuration(Configuration.Builder builder) throws KeyManagementException, NoSuchAlgorithmException {
service = Objects.requireNonNull(builder.service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
@ -162,8 +177,10 @@ public final class Configuration {
this.accountTwoPassword = builder.accountTwoPassword;
this.accountThreeUsername = builder.accountThreeUsername;
this.accountThreePassword = builder.accountThreePassword;
this.enabledTests = builder.enabledTests;
this.disabledTests = builder.disabledTests;
this.enabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.enabledTests);
this.enabledTestsMap = convertTestsToMap(enabledTests);
this.disabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.disabledTests);
this.disabledTestsMap = convertTestsToMap(disabledTests);
this.defaultConnectionNickname = builder.defaultConnectionNickname;
this.enabledConnections = builder.enabledConnections;
this.disabledConnections = builder.disabledConnections;
@ -192,6 +209,7 @@ public final class Configuration {
this.verbose = builder.verbose;
this.dnsResolver = builder.dnsResolver;
this.compatibilityMode = builder.compatibilityMode;
}
public boolean isAccountRegistrationPossible() {
@ -246,6 +264,8 @@ public final class Configuration {
private DnsResolver dnsResolver = DnsResolver.minidns;
private CompatibilityMode compatibilityMode = CompatibilityMode.standardsCompliant;
private Builder() {
}
@ -427,6 +447,20 @@ public final class Configuration {
return setDnsResolver(dnsResolver);
}
public Builder setCompatibilityMode(CompatibilityMode compatibilityMode) {
this.compatibilityMode = compatibilityMode;
return this;
}
public Builder setCompatibilityMode(String compatibilityModeString) {
if (compatibilityModeString == null) {
return this;
}
CompatibilityMode compatibilityMode = CompatibilityMode.valueOf(compatibilityModeString);
return setCompatibilityMode(compatibilityMode);
}
public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
return new Configuration(this);
}
@ -500,6 +534,8 @@ public final class Configuration {
builder.setDnsResolver(properties.getProperty("dnsResolver"));
builder.setCompatibilityMode(properties.getProperty("compatibilityMode"));
return builder.build();
}
@ -551,4 +587,112 @@ public final class Configuration {
});
}
private static Map<String, Set<String>> convertTestsToMap(Set<String> tests) {
Map<String, Set<String>> res = new HashMap<>();
for (String test : tests) {
String[] testParts = test.split("\\.");
if (testParts.length == 1) {
// The whole test specification does not contain a dot, assume it is a test class specification.
res.put(test, Collections.emptySet());
continue;
}
String lastTestPart = testParts[testParts.length - 1];
if (lastTestPart.isEmpty()) {
throw new IllegalArgumentException("Invalid test specifier: " + test);
}
char firstCharOfLastTestPart = lastTestPart.charAt(0);
if (!Character.isLowerCase(firstCharOfLastTestPart)) {
// The first character of the last test part is not lowercase, assume this is a fully qualified test
// class specification, e.g. org.foo.bar.TestClass.
res.put(test, Collections.emptySet());
}
// The first character of the last test part is lowercase, assume this is a test class *and* method name
// specification.
String testMethodName = lastTestPart;
int classPartsCount = testParts.length - 1;
String[] classParts = new String[classPartsCount];
System.arraycopy(testParts, 0, classParts, 0, classPartsCount);
String testClass = String.join(".", classParts);
res.compute(testClass, (k, v) -> {
if (v == null) {
v = new HashSet<>();
}
v.add(testMethodName);
return v;
});
}
return res;
}
private static Set<String> getKey(Class<?> testClass, Map<String, Set<String>> testsMap) {
String className = testClass.getName();
if (testsMap.containsKey(className)) {
return testsMap.get(className);
}
String unqualifiedClassName = testClass.getSimpleName();
if (testsMap.containsKey(unqualifiedClassName)) {
return testsMap.get(unqualifiedClassName);
}
return null;
}
private static boolean contains(Class<? extends AbstractSmackIntTest> testClass, Map<String, Set<String>> testsMap) {
Set<String> enabledMethods = getKey(testClass, testsMap);
return enabledMethods != null;
}
public boolean isClassEnabled(Class<? extends AbstractSmackIntTest> testClass) {
if (enabledTestsMap.isEmpty()) {
return true;
}
return contains(testClass, enabledTestsMap);
}
public boolean isClassDisabled(Class<? extends AbstractSmackIntTest> testClass) {
if (disabledTestsMap.isEmpty()) {
return false;
}
return contains(testClass, disabledTestsMap);
}
private static boolean contains(Method method, Map<String, Set<String>> testsMap) {
Class<?> testClass = method.getDeclaringClass();
Set<String> methods = getKey(testClass, testsMap);
if (methods == null) {
return false;
}
if (methods.isEmpty()) {
return true;
}
String methodName = method.getName();
return methods.contains(methodName);
}
public boolean isMethodEnabled(Method method) {
if (enabledTestsMap.isEmpty()) {
return true;
}
return contains(method, enabledTestsMap);
}
public boolean isMethodDisabled(Method method) {
if (disabledTestsMap.isEmpty()) {
return false;
}
return contains(method, disabledTestsMap);
}
}

View File

@ -282,13 +282,13 @@ public class SmackIntegrationTestFramework {
continue;
}
if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) {
if (!config.isClassEnabled(testClass)) {
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is not enabled");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
if (isInSet(testClass, config.disabledTests)) {
if (config.isClassDisabled(testClass)) {
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is disalbed");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
@ -377,14 +377,13 @@ public class SmackIntegrationTestFramework {
while (it.hasNext()) {
final Method method = it.next();
final String methodName = method.getName();
if (config.enabledTests != null && !(config.enabledTests.contains(methodName)
|| isInSet(testClass, config.enabledTests))) {
if (!config.isMethodEnabled(method)) {
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is not enabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
if (config.disabledTests != null && config.disabledTests.contains(methodName)) {
if (config.isMethodDisabled(method)) {
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is disabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
@ -607,15 +606,6 @@ public class SmackIntegrationTestFramework {
return (Exception) e;
}
private static boolean isInSet(Class<?> clz, Set<String> classes) {
if (classes == null) {
return false;
}
final String className = clz.getName();
final String unqualifiedClassName = clz.getSimpleName();
return classes.contains(className) || classes.contains(unqualifiedClassName);
}
public static final class TestRunResult {
/**

View File

@ -24,6 +24,7 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.roster.AbstractPresenceEventListener;
import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jivesoftware.smack.roster.Roster;
@ -99,7 +100,16 @@ public class IntegrationTestRosterUtil {
if (c2Entry == null) {
return;
}
roster.removeEntry(c2Entry);
try {
roster.removeEntry(c2Entry);
} catch (XMPPErrorException e) {
// Account for race conditions: server-sided, the item might already have been removed.
if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) {
// Trying to remove non-existing item. As it needs to be gone, this is fine.
return;
}
throw e;
}
}
}

View File

@ -23,8 +23,9 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.geoloc.GeoLocationManager;
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
import org.jivesoftware.smackx.pep.PepEventListener;
@ -35,7 +36,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jxmpp.jid.EntityBareJid;
import org.junit.jupiter.api.Assertions;
import org.jxmpp.util.XmppDateTime;
public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
@ -49,10 +50,21 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
glm2 = GeoLocationManager.getInstanceFor(conTwo);
}
@AfterClass
public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
}
/**
* Verifies that a notification is sent when a publication is received, assuming that notification filtering
* has been adjusted to allow for the notification to be delivered.
*
* @throws Exception if the test fails
*/
@SmackIntegrationTest
public void test() throws TimeoutException, Exception {
public void testNotification() throws Exception {
GeoLocation.Builder builder = GeoLocation.builder();
GeoLocation geoLocation1 = builder.setAccuracy(23d)
GeoLocation data = builder.setAccuracy(23d)
.setAlt(1000d)
.setAltAccuracy(10d)
.setArea("Delhi")
@ -77,31 +89,163 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
.build();
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
final PepEventListener<GeoLocation> geoLocationListener = new PepEventListener<GeoLocation>() {
@Override
public void onPepEvent(EntityBareJid jid, GeoLocation geoLocation, String id, Message message) {
if (geoLocation.equals(geoLocation1)) {
geoLocationReceived.signal();
} else {
geoLocationReceived.signalFailure("Received non matching GeoLocation");
}
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
final PepEventListener<GeoLocation> geoLocationListener = (jid, geoLocation, id, message) -> {
if (geoLocation.equals(data)) {
geoLocationReceived.signal();
}
};
glm2.addGeoLocationListener(geoLocationListener);
try {
glm1.publishGeoLocation(geoLocation1);
geoLocationReceived.waitForResult(timeout);
// Register ConTwo's interest in receiving geolocation notifications, and wait for that interest to have been propagated.
registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
// Publish the data.
glm1.publishGeoLocation(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
// Wait for the data to be received.
try {
Object result = geoLocationReceived.waitForResult(timeout);
// Explicitly assert the success case.
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
} catch (TimeoutException e) {
Assertions.fail("Expected to receive a PEP notification, but did not.");
}
} finally {
glm2.removeGeoLocationListener(geoLocationListener);
unregisterListener(glm2, geoLocationListener);
}
}
@AfterClass
public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
/**
* Verifies that a notification for a previously sent publication is received as soon as notification filtering
* has been adjusted to allow for the notification to be delivered.
*
* @throws Exception if the test fails
*/
@SmackIntegrationTest
public void testNotificationAfterFilterChange() throws Exception {
GeoLocation.Builder builder = GeoLocation.builder();
GeoLocation data = builder.setAccuracy(12d)
.setAlt(999d)
.setAltAccuracy(9d)
.setArea("Amsterdam")
.setBearing(9d)
.setBuilding("Test Building")
.setCountry("Netherlands")
.setCountryCode("NL")
.setDescription("My Description")
.setFloor("middle")
.setLat(25.098345d)
.setLocality("brilliant")
.setLon(77.992034)
.setPostalcode("110085")
.setRegion("North")
.setRoom("small")
.setSpeed(250.0d)
.setStreet("Wall Street")
.setText("Unit Testing GeoLocation 2")
.setTimestamp(XmppDateTime.parseDate("2007-02-19"))
.setTzo("+5:30")
.setUri(new URI("http://xmpp.org"))
.build();
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
final PepEventListener<GeoLocation> geoLocationListener = (jid, geoLocation, id, message) -> {
if (geoLocation.equals(data)) {
geoLocationReceived.signal();
}
};
// TODO Ensure that pre-existing filtering notification excludes geolocation.
try {
// Publish the data
publishAndWait(glm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
// Adds listener, which implicitly publishes a disco/info filter for geolocation notification.
registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
// Wait for the data to be received.
try {
Object result = geoLocationReceived.waitForResult(timeout);
// Explicitly assert the success case.
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
} catch (TimeoutException e) {
Assertions.fail("Expected to receive a PEP notification, but did not.");
}
} finally {
unregisterListener(glm2, geoLocationListener);
}
}
/**
* Registers a listener for GeoLocation data. This implicitly publishes a CAPS update to include a notification
* filter for the geolocation node. This method blocks until the server has indicated that this update has been
* received.
*
* @param geoManager The GeoLocationManager instance for the connection that is expected to receive data.
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
* @param listener A listener instance for GeoLocation data that is to be registered.
*
* @throws Exception if the test fails
*/
public void registerListenerAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, PepEventListener<GeoLocation> listener) throws Exception {
final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
if (info.containsFeature(GeoLocationManager.GEOLOCATION_NODE + "+notify")) {
notificationFilterReceived.signal();
}
};
discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
try {
geoManager.addGeoLocationListener(listener);
notificationFilterReceived.waitForResult(timeout);
} finally {
discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
}
}
/**
* The functionally reverse of {@link #registerListenerAndWait(GeoLocationManager, ServiceDiscoveryManager, PepEventListener)}
* with the difference of not being a blocking operation.
*
* @param geoManager The GeoLocationManager instance for the connection that was expected to receive data.
* @param listener A listener instance for GeoLocation data that is to be removed.
*/
public void unregisterListener(GeoLocationManager geoManager, PepEventListener<GeoLocation> listener) {
// Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
geoManager.removeGeoLocationListener(listener);
}
/**
* Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
*
* @param geoManager The GeoLocationManager instance for the connection that is expected to publish data.
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
* @param data The data to be published.
*
* @throws Exception if the test fails
*/
public void publishAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, GeoLocation data) throws Exception {
final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
final PepEventListener<GeoLocation> publicationEchoListener = (jid, geoLocation, id, message) -> {
if (geoLocation.equals(data)) {
publicationEchoReceived.signal();
}
};
try {
registerListenerAndWait(geoManager, discoManager, publicationEchoListener);
geoManager.addGeoLocationListener(publicationEchoListener);
geoManager.publishGeoLocation(data);
} finally {
geoManager.removeGeoLocationListener(publicationEchoListener);
}
}
}

View File

@ -16,9 +16,13 @@
*/
package org.jivesoftware.smackx.mood;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.mood.element.MoodElement;
import org.jivesoftware.smackx.pep.PepEventListener;
@ -28,6 +32,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.junit.jupiter.api.Assertions;
public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
@ -40,32 +45,155 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
mm2 = MoodManager.getInstanceFor(conTwo);
}
@SmackIntegrationTest
public void test() throws Exception {
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
if (moodElement.getMood() == Mood.satisfied) {
moodReceived.signal();
}
};
mm2.addMoodListener(moodListener);
try {
mm1.setMood(Mood.satisfied);
moodReceived.waitForResult(timeout);
} finally {
mm2.removeMoodListener(moodListener);
}
}
@AfterClass
public void unsubscribe()
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
}
/**
* Verifies that a notification is sent when a publication is received, assuming that notification filtering
* has been adjusted to allow for the notification to be delivered.
*
* @throws Exception if the test fails
*/
@SmackIntegrationTest
public void testNotification() throws Exception {
Mood data = Mood.satisfied;
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
if (moodElement.getMood().equals(data)) {
moodReceived.signal();
}
};
try {
// Register ConTwo's interest in receiving mood notifications, and wait for that interest to have been propagated.
registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
// Publish the data.
mm1.setMood(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
// Wait for the data to be received.
try {
moodReceived.waitForResult(timeout);
} catch (TimeoutException e) {
Assertions.fail("Expected to receive a PEP notification, but did not.");
}
} finally {
unregisterListener(mm2, moodListener);
}
}
/**
* Verifies that a notification for a previously sent publication is received as soon as notification filtering
* has been adjusted to allow for the notification to be delivered.
*
* @throws Exception if the test fails
*/
@SmackIntegrationTest
public void testNotificationAfterFilterChange() throws Exception {
Mood data = Mood.cautious;
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
if (moodElement.getMood().equals(data)) {
moodReceived.signal();
}
};
// TODO Ensure that pre-existing filtering notification excludes mood.
try {
// Publish the data
publishAndWait(mm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
// Adds listener, which implicitly publishes a disco/info filter for mood notification.
registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
// Wait for the data to be received.
try {
Object result = moodReceived.waitForResult(timeout);
// Explicitly assert the success case.
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
} catch (TimeoutException e) {
Assertions.fail("Expected to receive a PEP notification, but did not.");
}
} finally {
unregisterListener(mm2, moodListener);
}
}
/**
* Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification
* filter for the mood node. This method blocks until the server has indicated that this update has been
* received.
*
* @param moodManager The MoodManager instance for the connection that is expected to receive data.
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
* @param listener A listener instance for Mood data that is to be registered.
*
* @throws Exception if the test fails
*/
public void registerListenerAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, PepEventListener<MoodElement> listener) throws Exception {
final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
if (info.containsFeature(MoodManager.MOOD_NODE + "+notify")) {
notificationFilterReceived.signal();
}
};
discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
try {
moodManager.addMoodListener(listener);
notificationFilterReceived.waitForResult(timeout);
} finally {
discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
}
}
/**
* The functionally reverse of {@link #registerListenerAndWait(MoodManager, ServiceDiscoveryManager, PepEventListener)}
* with the difference of not being a blocking operation.
*
* @param moodManager The MoodManager instance for the connection that was expected to receive data.
* @param listener A listener instance for Mood data that is to be removed.
*/
public void unregisterListener(MoodManager moodManager, PepEventListener<MoodElement> listener) {
// Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
moodManager.removeMoodListener(listener);
}
/**
* Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
*
* @param moodManager The MoodManager instance for the connection that is expected to publish data.
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
* @param data The data to be published.
*
* @throws Exception if the test fails
*/
public void publishAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, Mood data) throws Exception {
final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
final PepEventListener<MoodElement> publicationEchoListener = (jid, moodElement, id, message) -> {
if (moodElement.getMood().equals(data)) {
publicationEchoReceived.signal();
}
};
try {
registerListenerAndWait(moodManager, discoManager, publicationEchoListener);
moodManager.addMoodListener(publicationEchoListener);
moodManager.setMood(data);
} finally {
moodManager.removeMoodListener(publicationEchoListener);
}
}
}

View File

@ -22,8 +22,6 @@ import java.util.List;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.xdata.form.FillableForm;
import org.jivesoftware.smackx.xdata.form.Form;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
@ -57,9 +55,8 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati
if (services.isEmpty()) {
throw new TestNotPossibleException("No MUC (XEP-45) service found");
}
else {
mucService = services.get(0);
}
mucService = services.get(0);
}
/**
@ -90,18 +87,57 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati
muc.destroy("test fixture teardown", null);
}
static void createMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName));
if (handle != null) {
handle.makeInstant();
}
static void createMuc(MultiUserChat muc, Resourcepart resourceName) throws
SmackException.NoResponseException, XMPPException.XMPPErrorException,
InterruptedException, MultiUserChatException.MucAlreadyJoinedException,
SmackException.NotConnectedException,
MultiUserChatException.MissingMucCreationAcknowledgeException,
MultiUserChatException.NotAMucServiceException {
muc.create(resourceName).makeInstant();
}
static void createModeratedMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
muc.create(Resourcepart.from(resourceName));
Form configForm = muc.getConfigurationForm();
FillableForm answerForm = configForm.getFillableForm();
answerForm.setAnswer("muc#roomconfig_moderatedroom", true); //TODO Add this to the MucConfigFormManager?
muc.sendConfigurationForm(answerForm);
static void createMuc(MultiUserChat muc, String nickname) throws
XmppStringprepException, MultiUserChatException.MucAlreadyJoinedException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException,
MultiUserChatException.MissingMucCreationAcknowledgeException,
SmackException.NoResponseException, InterruptedException,
MultiUserChatException.NotAMucServiceException {
createMuc(muc, Resourcepart.from(nickname));
}
static void createMembersOnlyMuc(MultiUserChat muc, Resourcepart resourceName) throws
SmackException.NoResponseException, XMPPException.XMPPErrorException,
InterruptedException, MultiUserChatException.MucAlreadyJoinedException,
SmackException.NotConnectedException,
MultiUserChatException.MissingMucCreationAcknowledgeException,
MultiUserChatException.MucConfigurationNotSupportedException,
MultiUserChatException.NotAMucServiceException {
muc.create(resourceName)
.getConfigFormManager()
.makeMembersOnly()
.submitConfigurationForm();
}
static void createModeratedMuc(MultiUserChat muc, Resourcepart resourceName)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException,
MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException,
MultiUserChatException.MissingMucCreationAcknowledgeException,
MultiUserChatException.NotAMucServiceException,
MultiUserChatException.MucConfigurationNotSupportedException {
muc.create(resourceName)
.getConfigFormManager()
.makeModerated()
.submitConfigurationForm();
}
static void createHiddenMuc(MultiUserChat muc, Resourcepart resourceName)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException,
MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException,
MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException,
MultiUserChatException.MucConfigurationNotSupportedException {
muc.create(resourceName)
.getConfigFormManager()
.makeHidden()
.submitConfigurationForm();
}
}

Some files were not shown because too many files have changed in this diff Show More