Introduce Smack's Modular Connection Architecture

This is a complete redesign of what was previously
XmppNioTcpConnection. The new architecture allows to extend an XMPP
client to server (c2s) connection with new transport bindings and
other extensions.
mix
Florian Schmaus 2 years ago
parent cec312fe64
commit cc636fff21
  1. 60
      build.gradle
  2. 35
      documentation/connection-modules.md
  3. 44
      documentation/developer/integrationtest.md
  4. 1
      documentation/index.md
  5. 1
      settings.gradle
  6. 5
      smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
  7. 2
      smack-core/build.gradle
  8. 54
      smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
  9. 50
      smack-core/src/main/java/org/jivesoftware/smack/AbstractXmppNioConnection.java
  10. 10
      smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java
  11. 19
      smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
  12. 126
      smack-core/src/main/java/org/jivesoftware/smack/SmackException.java
  13. 28
      smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
  14. 7
      smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java
  15. 6
      smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java
  16. 8
      smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java
  17. 77
      smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2Module.java
  18. 53
      smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2ModuleDescriptor.java
  19. 21
      smack-core/src/main/java/org/jivesoftware/smack/bind2/package-info.java
  20. 1114
      smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
  21. 167
      smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java
  22. 40
      smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModule.java
  23. 48
      smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
  24. 76
      smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java
  25. 125
      smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java
  26. 179
      smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/WalkStateGraphContext.java
  27. 21
      smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/package-info.java
  28. 21
      smack-core/src/main/java/org/jivesoftware/smack/c2s/package-info.java
  29. 132
      smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java
  30. 54
      smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModuleDescriptor.java
  31. 5
      smack-core/src/main/java/org/jivesoftware/smack/compression/zlib/ZlibXmppCompressionFactory.java
  32. 806
      smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
  33. 58
      smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateEvent.java
  34. 4
      smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateMachineListener.java
  35. 10
      smack-core/src/main/java/org/jivesoftware/smack/fsm/LoginContext.java
  36. 43
      smack-core/src/main/java/org/jivesoftware/smack/fsm/NoOpState.java
  37. 81
      smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
  38. 61
      smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java
  39. 26
      smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java
  40. 41
      smack-core/src/main/java/org/jivesoftware/smack/fsm/StateMachineException.java
  41. 87
      smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
  42. 85
      smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModule.java
  43. 54
      smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModuleDescriptor.java
  44. 23
      smack-core/src/main/java/org/jivesoftware/smack/isr/package-info.java
  45. 7
      smack-core/src/main/java/org/jivesoftware/smack/util/ArrayBlockingQueueWithShutdown.java
  46. 11
      smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java
  47. 204
      smack-core/src/main/java/org/jivesoftware/smack/util/DNSUtil.java
  48. 5
      smack-core/src/main/java/org/jivesoftware/smack/util/Function.java
  49. 13
      smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java
  50. 34
      smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
  51. 33
      smack-core/src/main/java/org/jivesoftware/smack/util/dns/DNSResolver.java
  52. 182
      smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java
  53. 91
      smack-core/src/main/java/org/jivesoftware/smack/util/dns/SRVRecord.java
  54. 59
      smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java
  55. 70
      smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java
  56. 66
      smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionException.java
  57. 31
      smack-core/src/main/java/org/jivesoftware/smack/util/rce/SingleAddressRemoteConnectionEndpoint.java
  58. 21
      smack-core/src/main/java/org/jivesoftware/smack/util/rce/package-info.java
  59. 59
      smack-core/src/test/java/org/jivesoftware/smack/SmackExceptionTest.java
  60. 12
      smack-core/src/test/java/org/jivesoftware/smack/util/DnsUtilTest.java
  61. 8
      smack-integration-test/build.gradle
  62. 4
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
  63. 4
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
  64. 6
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
  65. 9
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java
  66. 169
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
  67. 12
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java
  68. 363
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
  69. 76
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java
  70. 137
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java
  71. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/ChatTest.java
  72. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java
  73. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/StreamManagementTest.java
  74. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java
  75. 14
      smack-integration-test/src/main/java/org/jivesoftware/smack/XmppConnectionIntegrationTest.java
  76. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/AbstractChatIntegrationTest.java
  77. 2
      smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/IncomingMessageListenerIntegrationTest.java
  78. 2
      smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/OutgoingMessageListenerIntegrationTest.java
  79. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/roster/LowLevelRosterIntegrationTest.java
  80. 4
      smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java
  81. 11
      smack-integration-test/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionLowLevelIntegrationTest.java
  82. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/caps/EntityCapsTest.java
  83. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java
  84. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java
  85. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java
  86. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java
  87. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java
  88. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java
  89. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/iqversion/VersionIntegrationTest.java
  90. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java
  91. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
  92. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
  93. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatLowLevelIntegrationTest.java
  94. 4
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java
  95. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java
  96. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java
  97. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java
  98. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
  99. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java
  100. 2
      smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/AbstractOpenPgpIntegrationTest.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -14,6 +14,8 @@ buildscript {
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.0'
id 'net.ltgt.errorprone' version '1.1.1'
// Use e.g. "gradle <task> taskTree" to show its dependency tree.
id 'com.dorongold.task-tree' version '1.5'
}
apply plugin: 'org.kordamp.gradle.markdown'
@ -288,14 +290,7 @@ configure (junit4Projects) {
}
}
task copyAllJavadocDocFiles(type: Copy) {
from javadocAllProjects.collect { project ->
"${project.projectDir}/src/javadoc" }
into javadocAllDir
include '**/doc-files/*.*'
}
task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
task javadocAll(type: Javadoc) {
source javadocAllProjects.collect {project ->
project.sourceSets.main.allJava.findAll {
// Filter out symbolic links to avoid
@ -325,6 +320,25 @@ task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
] as String[]
overview = "$projectDir/resources/javadoc-overview.html"
}
// Finally copy the javadoc doc-files from the subprojects, which
// are potentially generated, to the javadocAll directory. Note
// that we use a copy *method* and not a *task* because the inputs
// of copy tasks is determined within the configuration phase. And
// since some of the inputs are generated, they will not get
// picked up if we used a copy method. See also
// https://stackoverflow.com/a/40518516/194894
doLast {
copy {
javadocAllProjects.each {
from ("${it.projectDir}/src/javadoc") {
include '**/doc-files/*.*'
}
}
into javadocAllDir
}
}
}
import org.apache.tools.ant.filters.ReplaceTokens
@ -494,31 +508,25 @@ subprojects {
}
// Work around https://github.com/gradle/gradle/issues/4046
javadoc.dependsOn('copyJavadocDocFiles')
task copyJavadocDocFiles(type: Copy) {
from('src/javadoc')
into 'build/docs/javadoc'
include '**/doc-files/*.*'
}
javadoc.dependsOn copyJavadocDocFiles
// If this subproject has a Makefile then make copyJavadocDocFiles
// and the root project's javadocAll task dependend on
// generateFiles.
if (file("$projectDir/Makefile").exists()) {
copyJavadocDocFiles.dependsOn('generateFiles')
rootProject.copyAllJavadocDocFiles.dependsOn("${project.name}:generateFiles")
task generateFiles(type: Exec) {
workingDir projectDir
commandLine 'make'
}
// Make sure root projects 'javadocAll' depends on the
// subproject's javadoc, to ensure that all all doc-files/ are
// generated and up-to-date. Obviously this means that the
// javadocAll task will also create the individual javadoc's of the
// subprojects.
javadocAll.dependsOn javadoc
}
clean.dependsOn('cleanGeneratedFiles')
rootProject.clean.dependsOn("${project.name}:cleanGeneratedFiles")
task cleanGeneratedFiles(type: Exec) {
workingDir projectDir
commandLine 'make', 'clean'
}
}
// The smack-java8-full project generates the dot and png files of the
// current state graph. Ensure they are generated before copied.
configure (project(':smack-java8-full')) {
copyJavadocDocFiles.dependsOn convertModularXmppClientToServerConnectionStateGraphDotToPng
}
configure (androidProjects + androidBootClasspathProjects) {

@ -0,0 +1,35 @@
Smack's Modular Connection Architecture
======================================
[Back](index.md)
**Note: Everything related to the modular connection architecture is currently considered experimental and should not be used in production. Use the mature `XMPPTCPConnection` if you do not feel adventurous.
Smack's modular connection architecture allows to extend a XMPP c2s (client-to-server) connection with additional functionalty by adding modules.
Those modules extend the Finite State Machine (FSM) within the `ModularXmppClientToServerConnection` with new states.
Connection modules can either be
- Transports
- Extensions
Transports bind the XMPP XML stream to an underlying transport like TCP, WebSockets, BOSH, and allow for the different particularities of transports like DirectTLS ([XEP-0368](https://xmpp.org/extensions/xep-0368.html)).
This eventually means that a single transport module can implement multiple transport mechanisms.
For example the TCP transport module implements the RFC6120 TCP and the XEP-0368 direct TLS TCP transport bindings.
Extensions allow for a richer functionality of the connection. Those include
- Compression
- zlib ([XEP-0138](https://xmpp.org/extensions/xep-0138.html))
- [Efficient XML Interchange (EXI)](https://www.w3.org/TR/exi/)
- Instant Stream Resumption ([XEP-0397](https://xmpp.org/extensions/xep-0397.html)
- Bind2
- Stream Management
Note that not all extensions work with every transport.
For example compression only works with TCP-based transport bindings.
Connection modules are plugged into the the modular connection via their constructor. and they usually declare backwards edges to some common, generic connection state of the FSM.
Modules and states always have an accompanying *descriptor* type.
`ModuleDescriptor` and `StateDescriptor` exist without an connection instance.
They describe the module and state metadata, while their modules are states are instanciated once a modular connection is instanciated.

@ -58,24 +58,28 @@ debugger=console
### Framework properties
| Name | |
|----------------------|-------------------------------------------|
| service | XMPP service to run the tests on |
| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
| securityMode | Either 'required' or 'disabled' |
| replyTimeout | In milliseconds |
| adminAccountUsername | Username of the XEP-0133 Admin account |
| adminAccountPassword | Password of the XEP-0133 Admin account |
| accountOneUsername | Username of the first XMPP account |
| accountOnePassword | Password of the first XMPP account |
| accountTwoUsername | Username of the second XMPP account |
| accountTwoPassword | Password of the second XMPP account |
| accountThreeUsername | Username of the third XMPP account |
| accountThreePassword | Password of the third XMPP account |
| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
| enabledTests | List of enabled tests |
| disabledTests | List of disabled tests |
| testPackages | List of packages with tests |
| Name | Description |
|----------------------|-----------------------------------------------------------------------------|
| service | XMPP service to run the tests on |
| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
| securityMode | Either 'required' or 'disabled' |
| replyTimeout | In milliseconds |
| adminAccountUsername | Username of the XEP-0133 Admin account |
| adminAccountPassword | Password of the XEP-0133 Admin account |
| accountOneUsername | Username of the first XMPP account |
| accountOnePassword | Password of the first XMPP account |
| accountTwoUsername | Username of the second XMPP account |
| accountTwoPassword | Password of the second XMPP account |
| accountThreeUsername | Username of the third XMPP account |
| accountThreePassword | Password of the third XMPP account |
| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
| enabledTests | List of enabled tests |
| disabledTests | List of disabled tests |
| defaultConnection | Nickname of the default connection |
| enabledConnections | List of enabled connection's nicknames |
| disabledConnections | List of disabled connection's nicknames |
| testPackages | List of packages with tests |
| verbose | If `true` set output to verbose |
### Where to place the properties file
@ -99,6 +103,10 @@ The base class that integration tests need to subclass.
Allows low level integration test, i.e. ever test method will have its on exclusive XMPPTCPConnection instances.
### `AbstractSmackSpecificLowLevelIntegrationTest`
Operates, like `AbstractSmackLowLevelIntegrationTest` on its own `XMPPConnection` instances, but is limited to a particular type of `XMPPConnection`.
### `IntegrationTestEnvironment`
The environment, e.g. the `XMPPConnections` provided to the integration tests by the framework. Note that for convenience `AbstractSmackIntegrationTest` contains some of those as protected members.

@ -13,3 +13,4 @@
* [Debugging with Smack](debugging.md)
* [Smack Extensions Manual](extensions/index.md)
* [Smack's Modular Connection Architecture](connection-modules.md)

@ -23,6 +23,7 @@ include 'smack-core',
'smack-android',
'smack-android-extensions',
'smack-java7',
'smack-java8-full',
'smack-integration-test',
'smack-omemo',
'smack-omemo-signal',

@ -27,7 +27,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.GenericConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection;
@ -136,6 +136,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
this.config = config;
}
@SuppressWarnings("deprecation")
@Override
protected void connectInternal() throws SmackException, InterruptedException {
done = false;
@ -177,7 +178,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
.setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0")
.build());
} catch (Exception e) {
throw new ConnectionException(e);
throw new GenericConnectionException(e);
}
// Wait for the response from the server

@ -25,6 +25,8 @@ dependencies {
testCompile "org.xmlunit:xmlunit-assertj:$xmlUnitVersion"
testCompile 'com.jamesmurty.utils:java-xmlbuilder:1.2'
testCompile 'org.bouncycastle:bcprov-jdk15on:1.64'
testCompile 'com.google.guava:guava:28.2-jre'
testCompile 'org.jgrapht:jgrapht-io:1.3.1'
}
class CreateFileTask extends DefaultTask {

@ -1,6 +1,6 @@
/**
*
* Copyright 2009 Jive Software, 2018-2019 Florian Schmaus.
* Copyright 2009 Jive Software, 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,7 +34,6 @@ import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@ -87,6 +86,7 @@ import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.filter.IQReplyFilter;
@ -136,7 +136,6 @@ import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -150,8 +149,6 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppStringUtils;
import org.minidns.dnsname.DnsName;
/**
* This abstract class is commonly used as super class for XMPP connection mechanisms like TCP and BOSH. Hence it
@ -394,7 +391,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* The used port to establish the connection to
*/
protected int port;
protected UInt16 port;
/**
* Flag that indicates if the user is currently authenticated with the server.
@ -484,7 +481,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@Override
public int getPort() {
return port;
final UInt16 port = this.port;
if (port == null) {
return -1;
}
return port.intValue();
}
@Override
@ -525,6 +527,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
saslFeatureReceived.init();
lastFeaturesReceived.init();
tlsHandled.init();
closingStreamReceived.init();
}
/**
@ -778,38 +781,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private DomainBareJid xmppServiceDomain;
protected List<HostAddress> hostAddresses;
/**
* Populates {@link #hostAddresses} with the resolved addresses or with the configured host address. If no host
* address was configured and all lookups failed, for example with NX_DOMAIN, then {@link #hostAddresses} will be
* populated with the empty list.
*
* @return a list of host addresses where DNS (SRV) RR resolution failed.
*/
protected List<HostAddress> populateHostAddresses() {
List<HostAddress> failedAddresses = new LinkedList<>();
if (config.hostAddress != null) {
hostAddresses = new ArrayList<>(1);
HostAddress hostAddress = new HostAddress(config.port, config.hostAddress);
hostAddresses.add(hostAddress);
}
else if (config.host != null) {
hostAddresses = new ArrayList<>(1);
HostAddress hostAddress = DNSUtil.getDNSResolver().lookupHostAddress(config.host, config.port, failedAddresses, config.getDnssecMode());
if (hostAddress != null) {
hostAddresses.add(hostAddress);
}
} else {
// N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName
DnsName dnsName = DnsName.from(config.getXMPPServiceDomain());
hostAddresses = DNSUtil.resolveXMPPServiceDomain(dnsName, failedAddresses, config.getDnssecMode());
}
// Either the populated host addresses are not empty *or* there must be at least one failed address.
assert !hostAddresses.isEmpty() || !failedAddresses.isEmpty();
return failedAddresses;
}
protected Lock getConnectionLock() {
return connectionLock;
}
@ -980,6 +951,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
tlsHandled.reportGenericFailure(smackWrappedException);
saslFeatureReceived.reportGenericFailure(smackWrappedException);
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
closingStreamReceived.reportFailure(smackWrappedException);
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
// XMPPTCPConnection. Create delegation method?
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
@ -2182,6 +2154,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
CACHED_EXECUTOR_SERVICE.execute(runnable);
}
protected final SmackReactor getReactor() {
return SMACK_REACTOR;
}
protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
return SMACK_REACTOR.schedule(runnable, delay, unit);
}

@ -1,50 +0,0 @@
/**
*
* Copyright 2018 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;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
public abstract class AbstractXmppNioConnection extends AbstractXmppStateMachineConnection {
protected AbstractXmppNioConnection(ConnectionConfiguration configuration, GraphVertex<StateDescriptor> initialStateDescriptorVertex) {
super(configuration, initialStateDescriptorVertex);
}
protected SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
return SMACK_REACTOR.registerWithSelector(channel, ops, callback);
}
/**
* Set the interest Ops of a SelectionKey. Since Java's NIO interestOps(int) can block at any time, we use a queue
* to perform the actual operation in the reactor where we can perform this operation non-blocking.
*
* @param selectionKey TODO javadoc me please
* @param interestOps TODO javadoc me please
*/
protected void setInterestOps(SelectionKey selectionKey, int interestOps) {
SMACK_REACTOR.setInterestOps(selectionKey, interestOps);
}
}

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2017-2019 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2017-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -229,14 +229,18 @@ public abstract class ConnectionConfiguration {
}
DnsName getHost() {
public DnsName getHost() {
return host;
}
InetAddress getHostAddress() {
public InetAddress getHostAddress() {
return hostAddress;
}
public int getPort() {
return port;
}
/**
* Returns the server name of the target server.
*

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003-2007 Jive Software, 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,6 +28,8 @@ import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
@ -379,4 +381,19 @@ public final class SmackConfiguration {
return defaultConcurrencyLevelLimit;
}
private static final Set<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>> KNOWN_MODULES = new HashSet<>();
public static boolean addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor) {
synchronized (KNOWN_MODULES) {
return KNOWN_MODULES.add(moduleDescriptor);
}
}
public static void addAllKnownModulesTo(ModularXmppClientToServerConnectionConfiguration.Builder builder) {
synchronized (KNOWN_MODULES) {
for (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor : KNOWN_MODULES) {
builder.addModule(moduleDescriptor);
}
}
}
}

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2019 Florian Schmaus
* Copyright 2014-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
*/
package org.jivesoftware.smack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsFailed;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.util.rce.RemoteConnectionException;
import org.jxmpp.jid.Jid;
@ -90,6 +94,7 @@ public abstract class SmackException extends Exception {
public static NoResponseException newWith(XMPPConnection connection, String waitingFor) {
final StringBuilder sb = getWaitingFor(connection);
sb.append(" While waiting for ").append(waitingFor);
sb.append(" [").append(connection).append(']');
return new NoResponseException(sb.toString());
}
@ -264,45 +269,112 @@ public abstract class SmackException extends Exception {
}
}
public abstract static class ConnectionException extends SmackException {
private static final long serialVersionUID = 1L;
protected ConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
protected ConnectionException(String message) {
super(message);
}
}
public static final class GenericConnectionException extends ConnectionException {
private static final long serialVersionUID = 1L;
/**
* Deprecated, do not use.
*
* @param wrappedThrowable the wrapped throwable.
*/
@Deprecated
public GenericConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
}
/**
* ConnectionException is thrown if Smack is unable to connect to all hosts of a given XMPP
* service. The failed hosts can be retrieved with
* {@link ConnectionException#getFailedAddresses()}, which will have the exception causing the
* connection failure set and retrievable with {@link HostAddress#getExceptions()}.
* This exception is thrown if Smack is unable to connect to all hosts of a given XMPP
* service. The connection exceptions can be retrieved with
* {@link EndpointConnectionException#getConnectionExceptions()}, which will have the exception causing the
* connection failure set and retrievable with {@link RemoteConnectionException#getException()}.
*/
public static class ConnectionException extends SmackException {
public static final class EndpointConnectionException extends ConnectionException {
/**
*
*/
private static final long serialVersionUID = 1686944201672697996L;
private static final long serialVersionUID = 1;
private final List<HostAddress> failedAddresses;
private final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
private final List<? extends RemoteConnectionException<?>> connectionExceptions;
public ConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
failedAddresses = new ArrayList<>(0);
private EndpointConnectionException(String message, List<RemoteConnectionEndpointLookupFailure> lookupFailures,
List<? extends RemoteConnectionException<?>> connectionExceptions) {
super(message);
// At least one list must contain an entry.
assert !lookupFailures.isEmpty() || !connectionExceptions.isEmpty();
this.lookupFailures = lookupFailures;
this.connectionExceptions = connectionExceptions;
}
public static EndpointConnectionException from(List<RemoteConnectionEndpointLookupFailure> lookupFailures,
List<? extends RemoteConnectionException<?>> connectionExceptions) {
StringBuilder sb = new StringBuilder(256);
if (!lookupFailures.isEmpty()) {
sb.append("Could not lookup the following endpoints: ");
StringUtils.appendTo(lookupFailures, sb);
}
if (!connectionExceptions.isEmpty()) {
sb.append("The following addresses failed: ");
StringUtils.appendTo(connectionExceptions, sb, rce -> sb.append(rce.getErrorMessage()));
}
return new EndpointConnectionException(sb.toString(), lookupFailures, connectionExceptions);
}
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
return lookupFailures;
}
private ConnectionException(String message, List<HostAddress> failedAddresses) {
public List<? extends RemoteConnectionException<? extends RemoteConnectionEndpoint>> getConnectionExceptions() {
return connectionExceptions;
}
}
public static final class NoEndpointsDiscoveredException extends ConnectionException {
private static final long serialVersionUID = 1L;
private final List<LookupConnectionEndpointsFailed> lookupFailures;
private NoEndpointsDiscoveredException(String message, List<LookupConnectionEndpointsFailed> lookupFailures) {
super(message);
this.failedAddresses = failedAddresses;
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
}
public static ConnectionException from(List<HostAddress> failedAddresses) {
final String DELIMITER = ", ";
StringBuilder sb = new StringBuilder("The following addresses failed: ");
for (HostAddress hostAddress : failedAddresses) {
sb.append(hostAddress.getErrorMessage());
sb.append(DELIMITER);
}
// Remove the last delimiter
sb.setLength(sb.length() - DELIMITER.length());
return new ConnectionException(sb.toString(), failedAddresses);
public List<LookupConnectionEndpointsFailed> getLookupFailures() {
return lookupFailures;
}
public List<HostAddress> getFailedAddresses() {
return failedAddresses;
public static NoEndpointsDiscoveredException from(List<LookupConnectionEndpointsFailed> lookupFailures) {
StringBuilder sb = new StringBuilder();
if (lookupFailures.isEmpty()) {
sb.append("No endpoint lookup finished within the timeout");
} else {
sb.append("Not endpoints could be discovered due the following lookup failures: ");
StringUtils.appendTo(lookupFailures, sb);
}
return new NoEndpointsDiscoveredException(sb.toString(), lookupFailures);
}
}

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2018 Florian Schmaus
* Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,9 @@ package org.jivesoftware.smack;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -31,6 +33,7 @@ import javax.net.SocketFactory;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.CallbackRecipient;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.ExceptionCallback;
import org.jivesoftware.smack.util.SuccessCallback;
@ -48,6 +51,8 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
private ExceptionCallback<E> exceptionCallback;
private Consumer<SmackFuture<V, E>> completionCallback;
@Override
public final synchronized boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
@ -87,6 +92,11 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return this;
}
public void onCompletion(Consumer<SmackFuture<V, E>> completionCallback) {
this.completionCallback = completionCallback;
maybeInvokeCallbacks();
}
private V getOrThrowExecutionException() throws ExecutionException {
assert result != null || exception != null || cancelled;
if (result != null) {
@ -148,11 +158,19 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return getOrThrowExecutionException();
}
public V getIfAvailable() {
return result;
}
protected final synchronized void maybeInvokeCallbacks() {
if (cancelled) {
return;
}
if ((result != null || exception != null) && completionCallback != null) {
completionCallback.accept(this);
}
if (result != null && successCallback != null) {
AbstractXMPPConnection.asyncGo(new Runnable() {
@Override
@ -308,4 +326,12 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return future;
}
public static boolean await(Collection<? extends SmackFuture<?, ?>> futures, long timeout, TimeUnit unit) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(futures.size());
for (SmackFuture<?, ?> future : futures) {
future.onCompletion(f -> latch.countDown());
}
return latch.await(timeout, unit);
}
}

@ -26,12 +26,15 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.bind2.Bind2ModuleDescriptor;
import org.jivesoftware.smack.compress.provider.CompressedProvider;
import org.jivesoftware.smack.compress.provider.FailureProvider;
import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream;
import org.jivesoftware.smack.compression.XmppCompressionManager;
import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.isr.InstantStreamResumptionModuleDescriptor;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider;
@ -136,6 +139,10 @@ public final class SmackInitialization {
ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE);
ProviderManager.addNonzaProvider(FailureProvider.INSTANCE);
SmackConfiguration.addModule(Bind2ModuleDescriptor.class);
SmackConfiguration.addModule(CompressionModuleDescriptor.class);
SmackConfiguration.addModule(InstantStreamResumptionModuleDescriptor.class);
SmackConfiguration.smackInitialized = true;
}

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -116,7 +116,7 @@ public class SmackReactor {
setReactorThreadCount(DEFAULT_REACTOR_THREAD_COUNT);
}
SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
public SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
SelectionKeyAttachment selectionKeyAttachment = new SelectionKeyAttachment(callback);
@ -129,7 +129,7 @@ public class SmackReactor {
}
}
void setInterestOps(SelectionKey selectionKey, int interestOps) {
public void setInterestOps(SelectionKey selectionKey, int interestOps) {
SetInterestOps setInterestOps = new SetInterestOps(selectionKey, interestOps);
pendingSetInterestOps.add(setInterestOps);
selector.wakeup();

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -70,7 +70,7 @@ public interface XmppInputOutputFilter {
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
}
default Object getStats() {
return null;
}
Object getStats();
String getFilterName();
}

@ -0,0 +1,77 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.bind2;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.SaslAuthenticationStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateTransitionResult;
public class Bind2Module extends ModularXmppClientToServerConnectionModule<Bind2ModuleDescriptor> {
protected Bind2Module(Bind2ModuleDescriptor moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(moduleDescriptor, connectionInternal);
}
public static final class Bind2StateDescriptor extends StateDescriptor {
private Bind2StateDescriptor() {
super(Bind2State.class, 386, StateDescriptor.Property.notImplemented);
addPredeccessor(ConnectedButUnauthenticatedStateDescriptor.class);
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
declarePrecedenceOver(SaslAuthenticationStateDescriptor.class);
}
@Override
protected Bind2Module.Bind2State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
// This is the trick: the module is constructed prior the states, so we get the actual state out of the module by fetching the module from the connection.
Bind2Module bind2Module = connectionInternal.connection.getConnectionModuleFor(Bind2ModuleDescriptor.class);
return bind2Module.constructBind2State(this, connectionInternal);
}
}
private static final class Bind2State extends State {
private Bind2State(Bind2StateDescriptor bind2StateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(bind2StateDescriptor, connectionInternal);
}
@Override
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
}
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
throw new IllegalStateException("Bind2 not implemented");
}
}
public Bind2State constructBind2State(Bind2StateDescriptor bind2StateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new Bind2State(bind2StateDescriptor, connectionInternal);
}
}

@ -0,0 +1,53 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.bind2;
import java.util.Collections;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
public class Bind2ModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private static final Bind2ModuleDescriptor INSTANCE = new Bind2ModuleDescriptor();
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
return Collections.singleton(Bind2Module.Bind2StateDescriptor.class);
}
@Override
protected Bind2Module constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new Bind2Module(this, connectionInternal);
}
public static class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
protected Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
super(connectionConfigurationBuilder);
}
@Override
protected Bind2ModuleDescriptor build() {
return INSTANCE;
}
}
}

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Classes and interfaces for Bind 2.0 (XEP-0386).
*/
package org.jivesoftware.smack.bind2;

@ -0,0 +1,167 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.c2s;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
import org.jivesoftware.smack.util.CollectionUtil;
public final class ModularXmppClientToServerConnectionConfiguration extends ConnectionConfiguration {
final Set<ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptors;
final GraphVertex<StateDescriptor> initialStateDescriptorVertex;
private ModularXmppClientToServerConnectionConfiguration(Builder builder) {
super(builder);
moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values()));
Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors = new HashSet<>();
// Add backward edges from configured connection modules. Note that all state descriptors from module
// descriptors are backwards edges.
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
Set<Class<? extends StateDescriptor>> moduleStateDescriptors = moduleDescriptor.getStateDescriptors();
backwardEdgeStateDescriptors.addAll(moduleStateDescriptors);
}
try {
initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
// TODO: Depending on the exact exception thrown, this potentially indicates an invalid connection
// configuration, e.g. there is no edge from disconnected to connected.
throw new IllegalStateException(e);
}
}
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(initialStateDescriptorVertex), pw,
breakStateName);
}
public String getStateGraphInDotFormat() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
printStateGraphInDotFormat(pw, true);
return sw.toString();
}
public static Builder builder() {
return new Builder();
}
public static final class Builder
extends ConnectionConfiguration.Builder<Builder, ModularXmppClientToServerConnectionConfiguration> {
private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>();
private Builder() {
SmackConfiguration.addAllKnownModulesTo(this);
}
@Override
public ModularXmppClientToServerConnectionConfiguration build() {
return new ModularXmppClientToServerConnectionConfiguration(this);
}
void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) {
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass();
if (modulesDescriptors.containsKey(moduleDescriptorClass)) {
throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured");
}
modulesDescriptors.put(moduleDescriptorClass, connectionModule);
}
@SuppressWarnings("unchecked")
public Builder addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
Class<?>[] declaredClasses = moduleClass.getDeclaredClasses();
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> builderClass = null;
for (Class<?> declaredClass : declaredClasses) {
if (!ModularXmppClientToServerConnectionModuleDescriptor.Builder.class.isAssignableFrom(declaredClass)) {
continue;
}
builderClass = (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder>) declaredClass;
break;
}
if (builderClass == null) {
throw new IllegalArgumentException(
"Found no builder for " + moduleClass + ". Delcared classes: " + Arrays.toString(declaredClasses));
}
return with(builderClass).buildModule();
}
public <B extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> B with(
Class<? extends B> moduleDescriptorBuilderClass) {
Constructor<? extends B> moduleDescriptorBuilderCosntructor;
try {
moduleDescriptorBuilderCosntructor = moduleDescriptorBuilderClass.getDeclaredConstructor(
ModularXmppClientToServerConnectionConfiguration.Builder.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e);
}
moduleDescriptorBuilderCosntructor.setAccessible(true);
B moduleDescriptorBuilder;
try {
moduleDescriptorBuilder = moduleDescriptorBuilderCosntructor.newInstance(this);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
return moduleDescriptorBuilder;
}
public Builder removeModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
modulesDescriptors.remove(moduleClass);
return getThis();
}
public Builder removeAllModules() {
modulesDescriptors.clear();
return getThis();
}
@Override
protected Builder getThis() {
return this;
}
}
}

@ -0,0 +1,40 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
public abstract class ModularXmppClientToServerConnectionModule<MD extends ModularXmppClientToServerConnectionModuleDescriptor> {
protected final MD moduleDescriptor;
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
protected ModularXmppClientToServerConnectionModule(MD moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
this.moduleDescriptor = moduleDescriptor;
this.connectionInternal = connectionInternal;
}
public MD getModuleDescriptor() {
return moduleDescriptor;
}
protected XmppClientToServerTransport getTransport() {
return null;
}
}

@ -0,0 +1,48 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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,