Compare commits
54 commits
44ad01a346
...
19b20fefec
Author | SHA1 | Date | |
---|---|---|---|
|
19b20fefec | ||
|
ffd027cc7d | ||
|
50eb94850d | ||
|
9e9c233468 | ||
|
f6c85d9fb3 | ||
|
e3425706d9 | ||
|
24782558d6 | ||
|
6d2216858c | ||
|
f78eade4da | ||
|
2e79a6b718 | ||
|
bf3a27df9a | ||
|
9ec67611a0 | ||
|
3ba1aba2e3 | ||
|
ef0fc01505 | ||
|
d93fbacc35 | ||
|
27c8016522 | ||
|
d565354dea | ||
|
f00e585f3d | ||
|
6da9773bbf | ||
|
dee34b5efc | ||
|
6a90300ef6 | ||
|
507345ed7a | ||
|
324e54b81b | ||
|
a2a22883d2 | ||
|
c5bb15c631 | ||
|
92f1fe647b | ||
|
ae2c53f78c | ||
|
3a84a1ff47 | ||
|
07f157b00f | ||
|
c9a9982cef | ||
|
3f13d90a56 | ||
|
5295a856e4 | ||
|
92f253cc74 | ||
|
f65cf45b5c | ||
|
875fb7fcfd | ||
|
9486fd6176 | ||
|
c232ca5cb0 | ||
|
5455cfb3d2 | ||
|
c081585c26 | ||
|
fbd5761296 | ||
|
9eee3b8d76 | ||
|
e87da18495 | ||
|
167af86f79 | ||
|
2c05754018 | ||
|
5e1cd62021 | ||
|
c8d0e65ccc | ||
|
e768bc8bfb | ||
|
6eb1004d38 | ||
|
c77948bb91 | ||
|
711d7d92bd | ||
|
ccf60b018e | ||
b7dd26af05 | |||
|
8c359eed16 | ||
|
a82ae41c22 |
9
.github/workflows/ci.yml
vendored
|
@ -6,22 +6,23 @@ jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Smack
|
name: Build Smack
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java:
|
java:
|
||||||
- 11
|
- 11
|
||||||
- 15
|
|
||||||
env:
|
env:
|
||||||
PRIMARY_JAVA_VERSION: 11
|
PRIMARY_JAVA_VERSION: 11
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up JDK ${{ matrix.java }}
|
- name: Set up JDK ${{ matrix.java }}
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: temurin
|
||||||
|
|
||||||
# Caches
|
# Caches
|
||||||
- name: Cache Maven
|
- name: Cache Maven
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
# Smack Changelog
|
# Smack Changelog
|
||||||
|
|
||||||
|
# 4.4.6 -- 2022-06-29
|
||||||
|
|
||||||
|
### Bug
|
||||||
|
|
||||||
|
- [SMACK-926](https://igniterealtime.atlassian.net/browse/SMACK-926) Memory-leak in ServiceDiscoveryManager
|
||||||
|
- [SMACK-925](https://igniterealtime.atlassian.net/browse/SMACK-925) MultiUserChat Presence interceptors do not intercept
|
||||||
|
|
||||||
# 4.4.5 -- 2022-03-02
|
# 4.4.5 -- 2022-03-02
|
||||||
|
|
||||||
### Bug
|
### Bug
|
||||||
|
|
163
DEVELOPING.md
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
# Building Smack
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
Building Smack is as simple as
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@github.com:igniterealtime/Smack.git
|
||||||
|
cd Smack
|
||||||
|
gradle assemble
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mac
|
||||||
|
|
||||||
|
Smack requires a case-sensitive file system in order to build. Unfortunately, the macOS operating system is case-insensitive by default.
|
||||||
|
To get around this, you can create a case-sensitive disk image to work from.
|
||||||
|
|
||||||
|
1. Launch Disk Utility (Applications > Utilities)
|
||||||
|
2. Click the +, or go to Edit > Add APFS Volume
|
||||||
|
3. Give it a name, e.g. "Smack"
|
||||||
|
4. Change the format to "APFS (Case-sensitive)"
|
||||||
|
5. Click Add
|
||||||
|
|
||||||
|
It'll auto-mount into /Volumes, e.g. /Volumes/Smack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Volumes/Smack
|
||||||
|
git clone git@github.com:igniterealtime/Smack.git
|
||||||
|
cd Smack
|
||||||
|
gradle assemble
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
Smack requires a case-sensitive file system in order to build. Unfortunately, Windows NTFS is case-insensitive by default.
|
||||||
|
To get around this, you can set specific folders as case-sensitive (requires Windows 10 v1803 or higher).
|
||||||
|
|
||||||
|
In an Administrator console:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
fsutil.exe file SetCaseSensitiveInfo C:\git\Smack enable
|
||||||
|
cd \git\Smack
|
||||||
|
git clone git@github.com:igniterealtime/Smack.git
|
||||||
|
cd Smack
|
||||||
|
gradle assemble
|
||||||
|
```
|
||||||
|
|
||||||
|
# IDE Config
|
||||||
|
|
||||||
|
### Eclipse
|
||||||
|
|
||||||
|
Import IDE settings from `./resources/eclipse/` to configure proper ordering of imports and correct formatting that should pass the CheckStyle rules.
|
||||||
|
|
||||||
|
### IntelliJ IDEA
|
||||||
|
|
||||||
|
Import Java Code Style settings from `./resources/intellij/smack_formatter.xml` to configure import optimisation and code formatting to pass the CheckStyle rules when building or submitting PRs.
|
||||||
|
|
||||||
|
_We've noticed, at time of writing, that IntelliJ often requires a restart when applying new rules - no amount of OK/Apply will do the trick._
|
||||||
|
|
||||||
|
# Smack Providers
|
||||||
|
|
||||||
|
Providers are responsible for parsing the XMPP XML stream into new Java objects.
|
||||||
|
|
||||||
|
## Provider Design
|
||||||
|
|
||||||
|
Assume you want to parse the following stanza extension element
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<myExtension attrFoo='fourthyTwo'>
|
||||||
|
<myElement>Foo is greater then Bar</myElement>
|
||||||
|
<myInfo alpha='true' delta='-1337'/>
|
||||||
|
</myExtension>
|
||||||
|
```
|
||||||
|
|
||||||
|
then the related provider would look like this
|
||||||
|
|
||||||
|
```java
|
||||||
|
public MyExtension parse(XmlPullParser parser, int initialDepth) {
|
||||||
|
MyElement myElement = null;
|
||||||
|
MyInfo myInfo = null;
|
||||||
|
String attrFoo = parser.getAttributeValue("", "attrFoo");
|
||||||
|
|
||||||
|
// Main parsing loop, use a loop label instead of "boolean done"
|
||||||
|
outerloop: while(true) {
|
||||||
|
// Make sure to have already parse all attributes of the outermost element,
|
||||||
|
// i.e. 'attrFoo' of 'myExtension' in this example. Then advance the parser
|
||||||
|
XmlPullParser.Event event = parser.next();
|
||||||
|
|
||||||
|
// Use switch/case of int instead of a if/else-if cascade
|
||||||
|
switch (event) {
|
||||||
|
case START_ELEMENT:
|
||||||
|
// Determine the name of the element which start tag we are seeing
|
||||||
|
String name = parser.getName();
|
||||||
|
// We can use switch/case of Strings since Java7, make use of its advantages
|
||||||
|
// and collect the values of the sub elements. If the sub elements are more
|
||||||
|
// complex then those of this example, consider creating extra *private static*
|
||||||
|
// parsing methods for them.
|
||||||
|
switch(name) {
|
||||||
|
case "myElement":
|
||||||
|
// You should only use XmlPullParser.nextText() when the element is
|
||||||
|
// required to have a text.
|
||||||
|
myElement = new MyElement(parser.nextText());
|
||||||
|
break;
|
||||||
|
case "myInfo";
|
||||||
|
// Use ParserUtils to parse Java primitives
|
||||||
|
boolenan alpha = ParserUtils.getBooleanAttribute(parser, "alpha");
|
||||||
|
int delta = ParserUtils.getIntegerAttribute(parser, "delta");
|
||||||
|
myInfo = new MyInfo(alpha, delta);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case END_ELEMENT:
|
||||||
|
// The abort condition with the break labeled loop statement
|
||||||
|
if (parser.getDepth() == initialDepth) {
|
||||||
|
break outerloop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the actual class at the very end, design the classes as immutable as possible
|
||||||
|
return new MyExtension(attrFoo, myElement, myInfo);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
Use a `long` instead of `int` when the XML schema says `xs:unsignedInt`, because Java's `int` range is to small for this XML numeric data type.
|
||||||
|
|
||||||
|
# Stanzas
|
||||||
|
## General Rules
|
||||||
|
|
||||||
|
All classes which subclass `TopLevelStreamElement` and `ExtensionElement` must either
|
||||||
|
|
||||||
|
1. be immutable (and ideally provide a Builder)
|
||||||
|
2. implement `TypedCloneable`
|
||||||
|
|
||||||
|
and must be `Serializable`.
|
||||||
|
The reason that it must be either 1. or 2. is that it makes no sense to clone an inmutable instance.
|
||||||
|
The preferred option is 1.
|
||||||
|
|
||||||
|
Note that there is legacy code in Smack which does not follow these rules. Patches are welcome.
|
||||||
|
|
||||||
|
## ExtensionElement
|
||||||
|
|
||||||
|
Extension elements are XML elements that are used in various parts and levels of stanzas.
|
||||||
|
|
||||||
|
## The static `from(Stanza)` Method
|
||||||
|
|
||||||
|
Every ExtensionElement class must have a static `from()` method that retrieves that extension for a given Stanza (if any).
|
||||||
|
|
||||||
|
Sample Code
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static RSMSet from(Stanza) {
|
||||||
|
return packet.getExtension(ELEMENT, NAMESPACE);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes certain ExtensionElement's are only found in one stanza type, in that case, specify the parameter type. For example `public static CarbonExtension getFrom(Message)`.
|
|
@ -1,7 +1,7 @@
|
||||||
Smack
|
Smack
|
||||||
=====
|
=====
|
||||||
|
|
||||||
[![Build Status](https://github.com/igniterealtime/Smack/workflows/CI/badge.svg)](https://github.com/igniterealtime/Smack/actions?query=workflow%3A%22CI%22) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
|
[![Build Status](https://github.com/igniterealtime/Smack/workflows/CI/badge.svg)](https://github.com/igniterealtime/Smack/actions?query=workflow%3A%22CI%22) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://search.jabber.network/api/1.0/badge?address=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org)
|
||||||
|
|
||||||
About
|
About
|
||||||
-----
|
-----
|
||||||
|
@ -53,13 +53,6 @@ users should:
|
||||||
|
|
||||||
Please search for your issues in the bug tracker before reporting.
|
Please search for your issues in the bug tracker before reporting.
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
If you find Smack useful and feel like donating, then you can use one of the following systems:
|
|
||||||
|
|
||||||
- Donate via Bitcoin: 1LiU78z3498wu3jwRPKbvovKAHjTcpVbuK
|
|
||||||
|
|
||||||
Contact
|
Contact
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ allprojects {
|
||||||
smackMinAndroidSdk = 19
|
smackMinAndroidSdk = 19
|
||||||
junitVersion = '5.7.1'
|
junitVersion = '5.7.1'
|
||||||
commonsIoVersion = '2.6'
|
commonsIoVersion = '2.6'
|
||||||
bouncyCastleVersion = '1.70'
|
bouncyCastleVersion = '1.71'
|
||||||
guavaVersion = '30.1-jre'
|
guavaVersion = '30.1-jre'
|
||||||
mockitoVersion = '3.7.7'
|
mockitoVersion = '3.7.7'
|
||||||
orgReflectionsVersion = '0.9.11'
|
orgReflectionsVersion = '0.9.11'
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
index.md
|
|
|
@ -1,35 +0,0 @@
|
||||||
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 functionality 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 and states are instanciated once a modular connection is instanciated.
|
|
|
@ -1,58 +0,0 @@
|
||||||
Smack: XMPPConnection Management
|
|
||||||
================================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Creating a Connection
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The `org.jivesoftware.smack.XMPPConnection` interface manages your connection to
|
|
||||||
an XMPP server. The default implementation is the
|
|
||||||
`org.jivesoftware.smack.tcp.XMPPTCPConnection` class. The class contains three constructors. The simplest, `XMPPTCPConnection(CharSequence, String, String)` takes the username, password, and server name you'd like
|
|
||||||
to connect to as arguments. All default connection settings will be used:
|
|
||||||
|
|
||||||
* A DNS SRV lookup will be performed to find the exact address and port (typically 5222) that the server resides at.
|
|
||||||
* The maximum security possible will be negotiated with the server, including TLS encryption, but the connection will fall back to lower security settings if necessary.
|
|
||||||
* The XMPP resource name "Smack" will be used for the connection.
|
|
||||||
Alternatively, you can use the `XMPPTCPConnection(ConnectionConfiguration)`
|
|
||||||
constructor to specify advanced connection settings. Some of these settings
|
|
||||||
include:
|
|
||||||
|
|
||||||
* Manually specify the server address and port of the server rather than using a DNS SRV lookup.
|
|
||||||
* Enable connection compression.
|
|
||||||
* Customize security settings, such as flagging the connection to require TLS encryption in order to connect.
|
|
||||||
* Specify a custom connection resource name such as "Work" or "Home". Every connection by a user to a server must have a unique resource name. For the user "jsmith@example.com", the full address with resource might be "jsmith@example.com/Smack". With unique resource names, a user can be logged into the server from multiple locations at once, or using multiple devices. The presence priority value used with each resource will determine which particular connection receives messages to the bare address ("jsmith@example.com" in our example).
|
|
||||||
|
|
||||||
Connect and Disconnect
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create the configuration for this new connection
|
|
||||||
XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.builder();
|
|
||||||
configBuilder.setUsernameAndPassword("username", "password");
|
|
||||||
configBuilder.setResource("SomeResource");
|
|
||||||
configBuilder.setXmppDomain("jabber.org");
|
|
||||||
|
|
||||||
AbstractXMPPConnection connection = new XMPPTCPConnection(configBuilder.build());
|
|
||||||
// Connect to the server
|
|
||||||
connection.connect();
|
|
||||||
// Log into the server
|
|
||||||
connection.login();
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// Disconnect from the server
|
|
||||||
connection.disconnect();
|
|
||||||
```
|
|
||||||
|
|
||||||
By default Smack will try to reconnect the connection in case it was abruptly
|
|
||||||
disconnected. The reconnection manager will try to immediately
|
|
||||||
reconnect to the server and increase the delay between attempts as successive
|
|
||||||
reconnections keep failing._
|
|
||||||
|
|
||||||
In case you want to force a reconnection while the reconnection manager is
|
|
||||||
waiting for the next reconnection, you can just use _AbstractXMPPConnection#connect()_
|
|
||||||
and a new attempt will be made. If the manual attempt also failed then the
|
|
||||||
reconnection manager will still continue the reconnection job.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,62 +0,0 @@
|
||||||
Debugging with Smack
|
|
||||||
====================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Smack includes two built-in debugging consoles that will let you track
|
|
||||||
all XML traffic between the client and server. A lite debugger and an
|
|
||||||
enhanced debugger contained in `smack-debug.jar`, and a console debugger in `smack-core.jar`.
|
|
||||||
|
|
||||||
Debugging mode can be enabled in two different ways:
|
|
||||||
|
|
||||||
1. Add the following line of code **before** creating new connections:
|
|
||||||
|
|
||||||
`SmackConfiguration.DEBUG = true;`
|
|
||||||
|
|
||||||
2. Set the Java system property `smack.debugEnabled` to true. The system property can be set on the command line such as:
|
|
||||||
|
|
||||||
`java -Dsmack.debugEnabled=true SomeApp `
|
|
||||||
|
|
||||||
If you wish to explicitly disable debug mode in your application, including
|
|
||||||
using the command-line parameter, add the following line to your application
|
|
||||||
before opening new connections:
|
|
||||||
|
|
||||||
`SmackConfiguration.DEBUG = false;`
|
|
||||||
|
|
||||||
Smack uses the following logic to decide the debugger console to use:
|
|
||||||
|
|
||||||
1. It will first try use the debugger class specified in the Java system property `smack.debuggerClass`. If you need to develop your own debugger, implement the `SmackDebugger` interface and then set the system property on the command line such as:
|
|
||||||
|
|
||||||
`java -Dsmack.debuggerClass=my.company.com.MyDebugger SomeApp `
|
|
||||||
|
|
||||||
2. If step 1 fails then Smack will try to use the enhanced debugger. The file `smack-debug.jar` contains the enhanced debugger. Therefore you will need to place the jar file in the classpath. For situations where space is an issue you may want to only deploy `smack-core.jar` in which case the enhanced and lite debugger won't be available, but only the console debugger.
|
|
||||||
|
|
||||||
3. The last option if the previous two steps fail is to use the console debugger. The console debugger is a very good option for situations where you need to have low memory footprint.
|
|
||||||
|
|
||||||
Enhanced Debugger
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
![Full Debug Window](images/enhanceddebugger.png) When debugging mode is
|
|
||||||
enabled, a debug window will appear containing tabs for each new created
|
|
||||||
connection. The window will contain the following information:
|
|
||||||
|
|
||||||
* XMPPConnection tabs -- each tab shows debugging information related to the connection.
|
|
||||||
* Smack info tab -- shows information about Smack (e.g. Smack version, installed components, etc.). The connection tab will contain the following information:
|
|
||||||
* All Stanzas -- shows sent and received packets information parsed by Smack.
|
|
||||||
* Raw Sent Stanzas -- raw XML traffic generated by Smack and sent to the server.
|
|
||||||
* Raw Received Stanzas -- raw XML traffic sent by the server to the client.
|
|
||||||
* Ad-hoc message -- allows to send ad-hoc packets of any type.
|
|
||||||
* Information -- shows connection state and statistics.
|
|
||||||
|
|
||||||
Lite Debugger
|
|
||||||
-------------
|
|
||||||
|
|
||||||
![Lite Debug Window](images/debugwindow.gif) When debugging mode is enabled, a
|
|
||||||
debug window will appear when each new connection is created. The window will
|
|
||||||
contain the following information:
|
|
||||||
|
|
||||||
* Client Traffic (red text) -- raw XML traffic generated by Smack and sent to the server.
|
|
||||||
* Server Traffic (blue text) -- raw XML traffic sent by the server to the client.
|
|
||||||
* Interpreted Stanzas (green text) -- shows XML packets from the server as parsed by Smack. Right click on any of the panes to bring up a menu with the choices to copy of the contents to the system clipboard or to clear the contents of the pane.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,63 +0,0 @@
|
||||||
Building Smack
|
|
||||||
==============
|
|
||||||
|
|
||||||
Linux
|
|
||||||
-----
|
|
||||||
|
|
||||||
Building Smack is as simple as
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone git@github.com:igniterealtime/Smack.git
|
|
||||||
cd Smack
|
|
||||||
gradle assemble
|
|
||||||
```
|
|
||||||
|
|
||||||
Mac
|
|
||||||
---
|
|
||||||
|
|
||||||
Smack requires a case-sensitive file system in order to build. Unfortunately, the macOS operating system is case-insensitive by default.
|
|
||||||
To get around this, you can create a case-sensitive disk image to work from.
|
|
||||||
|
|
||||||
1. Launch Disk Utility (Applications > Utilities)
|
|
||||||
2. Click the +, or go to Edit > Add APFS Volume
|
|
||||||
3. Give it a name, e.g. "Smack"
|
|
||||||
4. Change the format to "APFS (Case-sensitive)"
|
|
||||||
5. Click Add
|
|
||||||
|
|
||||||
It'll auto-mount into /Volumes, e.g. /Volumes/Smack
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /Volumes/Smack
|
|
||||||
git clone git@github.com:igniterealtime/Smack.git
|
|
||||||
cd Smack
|
|
||||||
gradle assemble
|
|
||||||
```
|
|
||||||
|
|
||||||
Windows
|
|
||||||
-------
|
|
||||||
|
|
||||||
Smack requires a case-sensitive file system in order to build. Unfortunately, Windows NTFS is case-insensitive by default.
|
|
||||||
To get around this, you can set specific folders as case-sensitive (requires Windows 10 v1803 or higher).
|
|
||||||
|
|
||||||
In an Administrator console:
|
|
||||||
|
|
||||||
```batch
|
|
||||||
fsutil.exe file SetCaseSensitiveInfo C:\git\Smack enable
|
|
||||||
cd \git\Smack
|
|
||||||
git clone git@github.com:igniterealtime/Smack.git
|
|
||||||
cd Smack
|
|
||||||
gradle assemble
|
|
||||||
```
|
|
||||||
|
|
||||||
IDE Config
|
|
||||||
----------
|
|
||||||
|
|
||||||
### Eclipse
|
|
||||||
|
|
||||||
Import IDE settings from `./resources/eclipse/` to configure proper ordering of imports and correct formatting that should pass the CheckStyle rules.
|
|
||||||
|
|
||||||
### IntelliJ IDEA
|
|
||||||
|
|
||||||
Import Java Code Style settings from `./resources/intellij/smack_formatter.xml` to configure import optimisation and code formatting to pass the CheckStyle rules when building or submitting PRs.
|
|
||||||
|
|
||||||
_We've noticed, at time of writing, that IntelliJ often requires a restart when applying new rules - no amount of OK/Apply will do the trick._
|
|
|
@ -1,209 +0,0 @@
|
||||||
Smack's Integration Test Framework
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Introduction
|
|
||||||
------------
|
|
||||||
|
|
||||||
Smack's Integration Test Framework is used to run a set of tests against a real XMPP service.
|
|
||||||
The framework discovers on start-up the available tests by reflection.
|
|
||||||
|
|
||||||
Quickstart
|
|
||||||
----------
|
|
||||||
|
|
||||||
You can run the framework against an XMPP service with
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gradle integrationTest -Dsinttest.service=my.xmppservice.org
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the service needs to have In-Band Registration (IBR) enabled.
|
|
||||||
|
|
||||||
A better alternative to IBR is using XEP-0133: Service Administration
|
|
||||||
to create the throw away accounts used by the integration test
|
|
||||||
framework. Simply use
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gradle integrationTest -Dsinttest.service=my.xmppservice.org \
|
|
||||||
-Dsinttest.adminAccountUsername=admin \
|
|
||||||
-Dsinttest.adminAccountPassword=aeR0Wuub
|
|
||||||
```
|
|
||||||
|
|
||||||
to run Smack's integration test framework against `my.xmppservice.org`
|
|
||||||
with an admin account named `admin` and `aeR0Wuub` as password.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The framework is configured with a standard Java properties file.
|
|
||||||
This file simply contains key/value pairs, which are separated by an equals sign ("=").
|
|
||||||
The most important configuration value is the `service` value, it's also the only required setting.
|
|
||||||
|
|
||||||
The file properties can be overridden with Java system properties.
|
|
||||||
The name of a system property that is used by the framework needs to be prefixed with `sinttest.` (*S*mack *Int*egration *Test* Framework).
|
|
||||||
For example the `service` property becomes `sinttest.service`.
|
|
||||||
|
|
||||||
### Minimal example **properties** file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
service=example.org
|
|
||||||
```
|
|
||||||
|
|
||||||
### Another example **properties** file
|
|
||||||
|
|
||||||
```bash
|
|
||||||
service=example.org
|
|
||||||
serviceTlsPin=CERTSHA256:2F:92:C9:4D:30:58:E1:05:21:9A:57:59:5F:6E:25:9A:0F:BF:FF:64:1A:C3:4B:EC:06:7D:4A:6F:0A:D5:21:85
|
|
||||||
debugger=console
|
|
||||||
```
|
|
||||||
|
|
||||||
### Framework properties
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
| dnsResolver | One of 'minidns', 'javax' or 'dnsjava'. Defaults to 'minidns'. |
|
|
||||||
|
|
||||||
### Where to place the properties file
|
|
||||||
|
|
||||||
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
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
Package `org.igniterealtime.smack.inttest`
|
|
||||||
|
|
||||||
### `SmackIntegrationTestFramework`
|
|
||||||
|
|
||||||
Contains `public static void main` method, i.e. the entry point for the framework.
|
|
||||||
Here the available integration tests are discovered by means of reflection, the configuration is read and a `IntegrationTestEnvironment` instance created, which includes the XMPPConnections.
|
|
||||||
|
|
||||||
### `AbstractSmackIntegrationTest`
|
|
||||||
|
|
||||||
The base class that integration tests need to subclass.
|
|
||||||
|
|
||||||
### `AbstractSmackLowLevelIntegrationTest`
|
|
||||||
|
|
||||||
Allows low level integration test, i.e. every test method will have its own 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.
|
|
||||||
|
|
||||||
### `SmackIntegrationTest`
|
|
||||||
|
|
||||||
An annotation that needs to be added to all methods that represent a single integration test.
|
|
||||||
Annotated integration test methods must not take any arguments (i.e. their parameter count is 0), and should return void as it's not evaluated in any way.
|
|
||||||
The methods are supposed to throw an exception if their integration test fails.
|
|
||||||
|
|
||||||
### `TestNotPossibleException`
|
|
||||||
|
|
||||||
Can be thrown by test methods or constructors to signal that their test is not possible, e.g. because the service does not support the required feature.
|
|
||||||
|
|
||||||
Running the integration tests
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Smack's Gradle build system is configured with a special task called `integrationTest`, which means you can run the tests simply with
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gradle integrationTest -Dsinttest.service=my.xmppservice.org
|
|
||||||
```
|
|
||||||
|
|
||||||
If one of `accountOneUsername`, `accountOnePassword`, `accountTwoUsername` or `accountTwoPassword` is not configured, then the framework will automatically create the accounts on the service. Of course this requires account registration (IBR) to be enabled.
|
|
||||||
If the accounts got created automatically by the framework, then they will also be deleted at the end of the test.
|
|
||||||
|
|
||||||
Implementing Integration Tests
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
Create a new class which extends `AbstractSmackIntegrationTest`.
|
|
||||||
Every non-static method, including the constructor, of this class will have two XMPPConnections available to perform the integration tests with: `conOne` and `conTwo`.
|
|
||||||
You can use the constructor to check if the XMPP service does provide the required XMPP feature.
|
|
||||||
If it does not, simply throw a `TestNotPossibleException`.
|
|
||||||
|
|
||||||
Test methods must be `public`, take zero arguments i.e. declare no parameters and be annoated with `@SmackIntegrationTest`.
|
|
||||||
If the test method is not able to perform a test then it should throw a `TestNotPossibleException`.
|
|
||||||
|
|
||||||
### Rules for integration tests
|
|
||||||
|
|
||||||
Tests should not leave any traces on the service if they are finished, i.e. the service state at the end of the test must be equal to the state of the beginning.
|
|
||||||
It must be possible to run the tests in parallel.
|
|
||||||
|
|
||||||
### Why are there two mechanisms to signal that the test is not possible?
|
|
||||||
|
|
||||||
Because the XMPP service may provide a component that is required to perform a certain integration test, but that component may not support all features.
|
|
||||||
For example, the XMPP service may provides a PubSub (XEP-0060) component, but this component may not support all features of XEP-0060.
|
|
||||||
|
|
||||||
### Low-Level Integration Tests
|
|
||||||
|
|
||||||
Classes that implement low-level integration tests need to sublcass `AbstractSmackLowLevelIntegrationTest`.
|
|
||||||
The test methods can declare as many parameters as they need to, but every parameter must be of type `XMPPTCPConnection`.
|
|
||||||
The framework will automatically create, register and login the connections.
|
|
||||||
After the test is finished, the connections will be unregistered with the XMPP service and terminated.
|
|
||||||
|
|
||||||
Debugging Integration Tests
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
A test, like any other code, may not be perfect on the first attempt, and you may require more information in order to ascertain quite what's wrong.
|
|
||||||
|
|
||||||
### Smack Debugger options
|
|
||||||
|
|
||||||
As listed in the main Smack [Debugging](../debugging.md) doc, there are two built-in debuggers that could surface you more information. Using the 'enhanced' debugger config option listed above, you'll get the Smack Debug Window launching when your tests launch, and you'll get a stanza-by-stanza account of what happened on each connection, hopefully enough to diagnose what went wrong.
|
|
||||||
|
|
||||||
### Debugging in the IDE
|
|
||||||
|
|
||||||
If the output isn't enough, you may need to debug and inspect running code within the IDE. Depending on the IDE, in order to get execution to pause at your breakpoints, you may need to switch your configuration. Instead of running `gradle integrationTest`, instead run the `SmackIntegrationTestFramework` class directly with the same command-line options.
|
|
||||||
|
|
||||||
Running your own integration tests
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
The framework can be used to run your own tests residing outside of the framework's default package scope.
|
|
||||||
Simply set the `testPackages` property to a comma separated list of package names where the framework should look for integration tests.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gradle integrationTest -Dsinttest.service=my.xmppserivce.org -Dsinttest.testPackages=org.mypackage,org.otherpackage
|
|
||||||
```
|
|
|
@ -1,75 +0,0 @@
|
||||||
Smack Providers
|
|
||||||
===============
|
|
||||||
|
|
||||||
Providers are responsible for parsing the XMPP XML stream into new Java objects.
|
|
||||||
|
|
||||||
Provider Design
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Assume you want to parse the following stanza extension element
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<myExtension attrFoo='fourthyTwo'>
|
|
||||||
<myElement>Foo is greater then Bar</myElement>
|
|
||||||
<myInfo alpha='true' delta='-1337'/>
|
|
||||||
</myExtension>
|
|
||||||
```
|
|
||||||
|
|
||||||
then the related provider would look like this
|
|
||||||
|
|
||||||
```java
|
|
||||||
public MyExtension parse(XmlPullParser parser, int initialDepth) {
|
|
||||||
MyElement myElement = null;
|
|
||||||
MyInfo myInfo = null;
|
|
||||||
String attrFoo = parser.getAttributeValue("", "attrFoo");
|
|
||||||
|
|
||||||
// Main parsing loop, use a loop label instead of "boolean done"
|
|
||||||
outerloop: while(true) {
|
|
||||||
// Make sure to have already parse all attributes of the outermost element,
|
|
||||||
// i.e. 'attrFoo' of 'myExtension' in this example. Then advance the parser
|
|
||||||
XmlPullParser.Event event = parser.next();
|
|
||||||
|
|
||||||
// Use switch/case of int instead of a if/else-if cascade
|
|
||||||
switch (event) {
|
|
||||||
case START_ELEMENT:
|
|
||||||
// Determine the name of the element which start tag we are seeing
|
|
||||||
String name = parser.getName();
|
|
||||||
// We can use switch/case of Strings since Java7, make use of its advantages
|
|
||||||
// and collect the values of the sub elements. If the sub elements are more
|
|
||||||
// complex then those of this example, consider creating extra *private static*
|
|
||||||
// parsing methods for them.
|
|
||||||
switch(name) {
|
|
||||||
case "myElement":
|
|
||||||
// You should only use XmlPullParser.nextText() when the element is
|
|
||||||
// required to have a text.
|
|
||||||
myElement = new MyElement(parser.nextText());
|
|
||||||
break;
|
|
||||||
case "myInfo";
|
|
||||||
// Use ParserUtils to parse Java primitives
|
|
||||||
boolenan alpha = ParserUtils.getBooleanAttribute(parser, "alpha");
|
|
||||||
int delta = ParserUtils.getIntegerAttribute(parser, "delta");
|
|
||||||
myInfo = new MyInfo(alpha, delta);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case END_ELEMENT:
|
|
||||||
// The abort condition with the break labeled loop statement
|
|
||||||
if (parser.getDepth() == initialDepth) {
|
|
||||||
break outerloop;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the actual class at the very end, design the classes as immutable as possible
|
|
||||||
return new MyExtension(attrFoo, myElement, myInfo);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Common Pitfalls
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Use a `long` instead of `int` when the XML schema says `xs:unsignedInt`, because Java's `int` range is to small for this XML numeric data type.
|
|
|
@ -1,33 +0,0 @@
|
||||||
General Rules
|
|
||||||
=============
|
|
||||||
|
|
||||||
All classes which subclass `TopLevelStreamElement` and `ExtensionElement` must either
|
|
||||||
|
|
||||||
1. be immutable (and ideally provide a Builder)
|
|
||||||
2. implement `TypedCloneable`
|
|
||||||
|
|
||||||
and must be `Serializable`.
|
|
||||||
The reason that it must be either 1. or 2. is that it makes no sense to clone an inmutable instance.
|
|
||||||
The preferred option is 1.
|
|
||||||
|
|
||||||
Note that there is legacy code in Smack which does not follow these rules. Patches are welcome.
|
|
||||||
|
|
||||||
ExtensionElement
|
|
||||||
================
|
|
||||||
|
|
||||||
Extension elements are XML elements that are used in various parts and levels of stanzas.
|
|
||||||
|
|
||||||
The static `from(Stanza)` Method
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Every ExtensionElement class must have a static `from()` method that retrieves that extension for a given Stanza (if any).
|
|
||||||
|
|
||||||
Sample Code
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static RSMSet from(Stanza) {
|
|
||||||
return packet.getExtension(ELEMENT, NAMESPACE);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Sometimes certain ExtensionElement's are only found in one stanza type, in that case, specify the parameter type. For example `public static CarbonExtension getFrom(Message)`.
|
|
|
@ -1,75 +0,0 @@
|
||||||
DNSSEC and DANE
|
|
||||||
===============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
**DNSSEC and DANE support in Smack and MiniDNS is still in its
|
|
||||||
infancy. It should be considered experimental and not ready for
|
|
||||||
production use at this time.** We would like to see more thorough
|
|
||||||
testing and review by the security community. If you can help, then
|
|
||||||
please do not hesitate to contact us.
|
|
||||||
|
|
||||||
About
|
|
||||||
-----
|
|
||||||
|
|
||||||
DNSSEC ([RFC 4033](https://tools.ietf.org/html/rfc4033) and others)
|
|
||||||
authenticates DNS answers, positive and negative ones. This means that
|
|
||||||
if a DNS response secured by DNSSEC turns out to be authentic, then
|
|
||||||
you can be sure that the domain either exists, and that the returned
|
|
||||||
resource records (RRs) are the ones the domain owner authorized, or
|
|
||||||
that the domain does not exists and that nobody tried to fake its non
|
|
||||||
existence.
|
|
||||||
|
|
||||||
The tricky part is that an application using DNSSEC can not determine
|
|
||||||
whether a domain uses DNSSEC, does not use DNSSEC or if someone
|
|
||||||
downgraded your DNS query using DNSSEC to a response without DNSSEC.
|
|
||||||
|
|
||||||
[DANE](https://tools.ietf.org/html/rfc6698) allows the verification of
|
|
||||||
a TLS certificate with information stored in the DNS system and
|
|
||||||
secured by DNSSEC. Thus DANE requires DNSSEC.
|
|
||||||
|
|
||||||
Prerequisites
|
|
||||||
-------------
|
|
||||||
|
|
||||||
From the three DNS resolver providers (MiniDNS, javax, dnsjava)
|
|
||||||
supported by Smack only [MiniDNS](https://github.com/rtreffer/minidns)
|
|
||||||
currently supports DNSSEC. MiniDNS is the default resolver when
|
|
||||||
smack-android is used. For other configurations, make sure to add
|
|
||||||
smack-resolver-minidns to your dependencies and call
|
|
||||||
`MiniDnsResolver.setup()` prior using Smack (e.g. in a `static {}`
|
|
||||||
code block).
|
|
||||||
|
|
||||||
DNSSEC API
|
|
||||||
----------
|
|
||||||
|
|
||||||
Smack's DNSSEC API is very simple: Just use
|
|
||||||
`ConnectionConfiguration.Builder..setDnssecMode(DnssecMode)` to enable
|
|
||||||
DNSSEC. `DnssecMode` can be one of
|
|
||||||
|
|
||||||
- `disabled`
|
|
||||||
- `needsDnssec`
|
|
||||||
- `needsDnssecAndDane`
|
|
||||||
|
|
||||||
The default is `disabled`.
|
|
||||||
|
|
||||||
If `needsDnssec` is used, then Smack will only connect if the DNS
|
|
||||||
results required to determine a host for the XMPP domain could be
|
|
||||||
verified using DNSSEC.
|
|
||||||
|
|
||||||
If `needsDnssecAndDane` then DANE will be used to verify the XMPP
|
|
||||||
service's TLS certificate if STARTTLS is used. Note that you may want
|
|
||||||
to configure
|
|
||||||
`ConnectionConfiguration.Builder.setSecurityMode(SecurityMode.required)`
|
|
||||||
if you use this DNSSEC mode setting.
|
|
||||||
|
|
||||||
Best practices
|
|
||||||
--------------
|
|
||||||
|
|
||||||
We recommend that applications using Smack's DNSSEC API do not ask the
|
|
||||||
user if DNSSEC is avaialble. Instead they should check for DNSSEC
|
|
||||||
suport on every connection attempt. Once DNSSEC support has been
|
|
||||||
discovered, the application should use the `needsDnssec` mode for all
|
|
||||||
future connection attempts. The same scheme can be applied when using
|
|
||||||
DANE. This approach is similar to the scheme established by
|
|
||||||
to
|
|
||||||
["HTTP Strict Transport Security" (HSTS, RFC 6797)](https://tools.ietf.org/html/rfc6797).
|
|
|
@ -1,76 +0,0 @@
|
||||||
Blocking Command
|
|
||||||
================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows one to manage communications blocking.
|
|
||||||
|
|
||||||
* Check push notifications support
|
|
||||||
* Get blocking list
|
|
||||||
* Block contact
|
|
||||||
* Unblock contact
|
|
||||||
* Unblock all
|
|
||||||
* Check if a message has a blocked error
|
|
||||||
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0191](http://xmpp.org/extensions/xep-0191.html)
|
|
||||||
|
|
||||||
|
|
||||||
Get an instance of Blocking Command Manager
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
BlockingCommandManager blockingCommandManager = BlockingCommandManager.getInstanceFor(connection);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Check blocking command support
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
boolean isSupported = blockingCommandManager.isSupportedByServer();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Get block list
|
|
||||||
--------------
|
|
||||||
|
|
||||||
```
|
|
||||||
List<Jid> blockList = blockingCommandManager.getBlockList();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Block contact
|
|
||||||
-------------
|
|
||||||
|
|
||||||
```
|
|
||||||
blockingCommandManager.blockContacts(jids);
|
|
||||||
```
|
|
||||||
*jids* is a `java.util.List<Jid>`
|
|
||||||
|
|
||||||
|
|
||||||
Unblock contact
|
|
||||||
---------------
|
|
||||||
|
|
||||||
```
|
|
||||||
blockingCommandManager.unblockContacts(jids);
|
|
||||||
```
|
|
||||||
*jids* is a `java.util.List<Jid>`
|
|
||||||
|
|
||||||
|
|
||||||
Unblock all
|
|
||||||
-----------
|
|
||||||
|
|
||||||
```
|
|
||||||
blockingCommandManager.unblockAll();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Check if a message has a blocked error
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
BlockedErrorExtension.isInside(message));
|
|
||||||
```
|
|
||||||
*message* is a `Message`
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
Entity Capabilities
|
|
||||||
===================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
This section details the usage of Smacks implementation of Entity
|
|
||||||
Capabilities.
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0115: Entity Capabilities](http://xmpp.org/extensions/xep-0115.html)
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Entity Capabilities is an XMPP Protocol extension, which, in order to minimize
|
|
||||||
network impact, caches the capabilities of XMPP entities. Those capabilities
|
|
||||||
are determined with the help of the Service Discovery Protocol
|
|
||||||
([XEP-0030](http://xmpp.org/extensions/xep-0030.html)).
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Entity Capabilities work silently in the background when enabled. If the remote
|
|
||||||
XMPP entity does not support XEP-0115 but XEP-0030 then XEP-0030 mechanisms
|
|
||||||
are transparently used. You can enable or disable Entity Capabilities by using
|
|
||||||
_**EntityCapsManager**_.
|
|
||||||
|
|
||||||
The cache used by Smack for Entity Capabilities is non-persistent as default.
|
|
||||||
That is, the cache only uses memory. But it is also possible to set a
|
|
||||||
persistent Entity Capabilities cache, which is recommended.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
Enable Entity Capabilities
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get an instance of entity caps manager for the specified connection
|
|
||||||
EntityCapsManager mgr = EntityCapsManager.getInstanceFor(connection);
|
|
||||||
// Enable entity capabilities
|
|
||||||
mgr.enableEntityCaps();
|
|
||||||
```
|
|
||||||
|
|
||||||
Configure a persistent cache for Entity Capabilities
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get an instance of entity caps manager for the specified connection
|
|
||||||
EntityCapsManager mgr = EntityCapsManager.getInstanceFor(connection);
|
|
||||||
// Create an cache, see smackx.entitycaps.cache for pre-defined cache implementations
|
|
||||||
EntityCapsPersistentCache cache = new SimpleDirectoryPersistentCache(new File("/foo/cachedir"));
|
|
||||||
// Set the cache
|
|
||||||
mgr.setPersistentCache(cache);
|
|
||||||
```
|
|
|
@ -1,37 +0,0 @@
|
||||||
Consistent Colors
|
|
||||||
=================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Since XMPP can be used on multiple platforms at the same time,
|
|
||||||
it might be a good idea to render given Strings like nicknames in the same
|
|
||||||
color on all platforms to provide a consistent user experience.
|
|
||||||
|
|
||||||
The utility class `ConsistentColor` allows the generation of colors to a given
|
|
||||||
string following the specification of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
To generate a consistent color for a given string, call
|
|
||||||
```
|
|
||||||
float[] rgb = ConsistentColor.RGBFrom(input);
|
|
||||||
```
|
|
||||||
The resulting float array contains values for RGB in the range of 0 to 1.
|
|
||||||
|
|
||||||
## Color Deficiency Corrections
|
|
||||||
Some users might suffer from color vision deficiencies. To compensate those deficiencies,
|
|
||||||
the API allows for color correction. The color correction mode is a static value, which can be changed at any time.
|
|
||||||
|
|
||||||
To correct colors for users with red-green color deficiency use the following code:
|
|
||||||
```
|
|
||||||
ConsistentColor.activateRedGreenBlindnessCorrection();
|
|
||||||
```
|
|
||||||
|
|
||||||
For color correction for users with blue-blindness, call
|
|
||||||
```
|
|
||||||
ConsistentColor.activateBlueBlindnessCorrection();
|
|
||||||
```
|
|
||||||
|
|
||||||
To deactivate color vision deficiency correction, call
|
|
||||||
```
|
|
||||||
ConsistentColor.deactivateDeficiencyCorrection();
|
|
||||||
```
|
|
|
@ -1,145 +0,0 @@
|
||||||
Data Forms
|
|
||||||
==========
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows the exchange of structured data between users and applications for common
|
|
||||||
tasks such as registration and searching using Forms.
|
|
||||||
|
|
||||||
* Create a Form to fill out
|
|
||||||
* Answer a Form
|
|
||||||
|
|
||||||
**XEP related:** [XEP-4](http://www.xmpp.org/extensions/xep-0004.html)
|
|
||||||
|
|
||||||
Create a Form to fill out
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
An XMPP entity may need to gather data from another XMPP entity. Therefore,
|
|
||||||
the data-gathering entity will need to create a new Form, specify the fields
|
|
||||||
that will conform to the Form and finally send the Form to the data-providing
|
|
||||||
entity.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to create a Form to fill out use the _**Form**_'s constructor passing
|
|
||||||
the constant **DataForm.type.form** as the parameter. The next step is to create
|
|
||||||
the form fields and add them to the form. In order to create and customize a
|
|
||||||
_**FormField**_ use the _**FormField**_'s constructor specifying the variable
|
|
||||||
name of the field as the parameter. Then use **setType(FormField.Type type)** to set
|
|
||||||
the field's type (e.g. FormField.Type.hidden, FormField.Type.text_single).
|
|
||||||
Once we have the _**Form**_ instance and the _**FormFields**_, the last step is
|
|
||||||
to send **addField(FormField field)** for each field that we want to add to
|
|
||||||
the form.
|
|
||||||
|
|
||||||
Once the form to fill out is finished we will want to send it in a message.
|
|
||||||
Send **getDataFormToSend()** to the form and add the answer as an extension to
|
|
||||||
the message to send.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to create and send a form to fill out:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a new form to gather data
|
|
||||||
Form formToSend = new Form(DataForm.Type.form);
|
|
||||||
formToSend.setInstructions("Fill out this form to report your case.\nThe case will be created automatically.");
|
|
||||||
formToSend.setTitle("Case configurations");
|
|
||||||
|
|
||||||
// Add a hidden variable to the form
|
|
||||||
FormField field = new FormField("hidden_var");
|
|
||||||
field.setType(FormField.Type.hidden);
|
|
||||||
field.addValue("Some value for the hidden variable");
|
|
||||||
formToSend.addField(field);
|
|
||||||
|
|
||||||
// Add a fixed variable to the form
|
|
||||||
field = new FormField();
|
|
||||||
field.addValue("Section 1: Case description");
|
|
||||||
formToSend.addField(field);
|
|
||||||
|
|
||||||
// Add a text-single variable to the form
|
|
||||||
field = new FormField("name");
|
|
||||||
field.setLabel("Enter a name for the case");
|
|
||||||
field.setType(FormField.Type.text_single);
|
|
||||||
formToSend.addField(field);
|
|
||||||
|
|
||||||
// Add a text-multi variable to the form
|
|
||||||
field = new FormField("description");
|
|
||||||
field.setLabel("Enter a description");
|
|
||||||
field.setType(FormField.Type.text_multi);
|
|
||||||
formToSend.addField(field);
|
|
||||||
|
|
||||||
// Create a chat with "user2@host.com"
|
|
||||||
Chat chat = ChatManager.getInstanceFor(conn1).chatWith("user2@host.com" );
|
|
||||||
Message msg = new Message();
|
|
||||||
msg.setBody("To enter a case please fill out this form and send it back");
|
|
||||||
|
|
||||||
// Add the form to fill out to the message to send
|
|
||||||
msg.addExtension(formToSend.getDataFormToSend());
|
|
||||||
|
|
||||||
// Send the message with the form to fill out
|
|
||||||
chat.send(msg);
|
|
||||||
```
|
|
||||||
|
|
||||||
Answer a Form
|
|
||||||
-------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Under many situations an XMPP entity could receive a form to fill out. For
|
|
||||||
example, some hosts may require to fill out a form in order to register new
|
|
||||||
users. Smack lets the data-providing entity to complete the form in an easy
|
|
||||||
way and send it back to the data-gathering entity.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
The form to fill out contains useful information that could be used for
|
|
||||||
rendering the form. But it cannot be used to actually complete it. Instead
|
|
||||||
it's necessary to create a new form based on the original form whose purpose
|
|
||||||
is to hold all the answers.
|
|
||||||
|
|
||||||
In order to create a new _**Form**_ to complete based on the original
|
|
||||||
_**Form**_ just send **createAnswerForm()** to the original _**Form**_. Once
|
|
||||||
you have a valid form that can be completed, all you have to do is
|
|
||||||
send **setAnswer(String variable, String value)** to the form where variable
|
|
||||||
is the variable of the _**FormField**_ that you want to answer and value is
|
|
||||||
the String representation of the answer. If the answer consists of several
|
|
||||||
values you could then use **setAnswer(String variable, List values)** where
|
|
||||||
values is a List of Strings.
|
|
||||||
|
|
||||||
Once the form has been completed we will want to send it back in a message.
|
|
||||||
Send **getDataFormToSend()** to the form and add the answer as an extension to
|
|
||||||
the message to send back.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to retrieve a form to fill out, complete the
|
|
||||||
form and send it back:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get the message with the form to fill out
|
|
||||||
Chat chat2 = ChatManager.getInstanceFor(conn).addIncomingListener(
|
|
||||||
new IncomingChatMessageListener() {
|
|
||||||
@Override public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
|
||||||
// Retrieve the form to fill out from the message
|
|
||||||
Form formToRespond = Form.getFormFrom(message);
|
|
||||||
|
|
||||||
// Obtain the form to send with the replies
|
|
||||||
Form completedForm = formToRespond.createAnswerForm();
|
|
||||||
|
|
||||||
// Add the answers to the form
|
|
||||||
completedForm.setAnswer("name", "Credit card number invalid");
|
|
||||||
completedForm.setAnswer("description", "The ATM says that my credit card number is invalid");
|
|
||||||
|
|
||||||
Message msg2 = new Message();
|
|
||||||
msg2.setBody("To enter a case please fill out this form and send it back");
|
|
||||||
|
|
||||||
// Add the completed form to the message to send back
|
|
||||||
msg2.addExtension(completedForm.getDataFormToSend());
|
|
||||||
|
|
||||||
// Send the message with the completed form
|
|
||||||
chat.send(msg2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
|
@ -1,235 +0,0 @@
|
||||||
Service Discovery
|
|
||||||
=================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
The service discovery extension allows one to discover items and information about
|
|
||||||
XMPP entities. Follow these links to learn how to use this extension.
|
|
||||||
|
|
||||||
* Manage XMPP entity features
|
|
||||||
* Provide node information
|
|
||||||
* Discover items associated with an XMPP entity
|
|
||||||
* Discover information about an XMPP entity
|
|
||||||
* Publish publicly available items
|
|
||||||
|
|
||||||
**XEP related:** [XEP-30](http://www.xmpp.org/extensions/xep-0030.html)
|
|
||||||
|
|
||||||
Manage XMPP entity features
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Any XMPP entity may receive a discovery request and must answer with its
|
|
||||||
associated items or information. Therefore, your Smack client may receive a
|
|
||||||
discovery request that must respond to (i.e., if your client supports XHTML-
|
|
||||||
IM). This extension automatically responds to a discovery request with the
|
|
||||||
information that you previously configured.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to configure the supported features by your client you should first
|
|
||||||
obtain the ServiceDiscoveryManager associated with your XMPPConnection. To get
|
|
||||||
your ServiceDiscoveryManager send **getInstanceFor(connection)** to the class
|
|
||||||
_**ServiceDiscoveryManager**_ where connection is your XMPPConnection.
|
|
||||||
|
|
||||||
Once you have your ServiceDiscoveryManager you will be able to manage the
|
|
||||||
supported features. To register a new feature send **addFeature(feature)** to
|
|
||||||
your _**ServiceDiscoveryManager**_ where feature is a String that represents
|
|
||||||
the supported feature. To remove a supported feature send
|
|
||||||
**removeFeature(feature)** to your _**ServiceDiscoveryManager**_ where feature
|
|
||||||
is a String that represents the feature to remove.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to add and remove supported features:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Obtain the ServiceDiscoveryManager associated with my XMPP connection
|
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
||||||
// Register that a new feature is supported by this XMPP entity
|
|
||||||
discoManager.addFeature(namespace1);
|
|
||||||
// Remove the specified feature from the supported features
|
|
||||||
discoManager.removeFeature(namespace2);
|
|
||||||
```
|
|
||||||
|
|
||||||
Provide node information
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Your XMPP entity may receive a discovery request for items non-addressable as
|
|
||||||
a JID such as the MUC rooms where you are joined. In order to answer the
|
|
||||||
correct information it is necessary to configure the information providers
|
|
||||||
associated to the items/nodes within the Smack client.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to configure the associated nodes within the Smack client you will
|
|
||||||
need to create a NodeInformationProvider and register it with the
|
|
||||||
_**ServiceDiscoveryManager**_. To get your ServiceDiscoveryManager send
|
|
||||||
**getInstanceFor(connection)** to the class _**ServiceDiscoveryManager**_
|
|
||||||
where connection is your XMPPConnection.
|
|
||||||
|
|
||||||
Once you have your ServiceDiscoveryManager you will be able to register
|
|
||||||
information providers for the XMPP entity's nodes. To register a new node
|
|
||||||
information provider send **setNodeInformationProvider(String node,
|
|
||||||
NodeInformationProvider listener)** to your _**ServiceDiscoveryManager**_
|
|
||||||
where node is the item non-addressable as a JID and listener is the
|
|
||||||
_**NodeInformationProvider**_ to register. To unregister a
|
|
||||||
_**NodeInformationProvider**_ send **removeNodeInformationProvider(String
|
|
||||||
node)** to your _**ServiceDiscoveryManager**_ where node is the item non-
|
|
||||||
addressable as a JID whose information provider we want to unregister.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to register a NodeInformationProvider with a
|
|
||||||
ServiceDiscoveryManager that will provide information concerning a node named
|
|
||||||
"http://jabber.org/protocol/muc#rooms":
|
|
||||||
|
|
||||||
```
|
|
||||||
// Set the NodeInformationProvider that will provide information about the
|
|
||||||
// joined rooms whenever a disco request is received
|
|
||||||
ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(
|
|
||||||
"http://jabber.org/protocol/muc#rooms",
|
|
||||||
new NodeInformationProvider() {
|
|
||||||
public List<DiscoverItems.Item> getNodeItems() {
|
|
||||||
MultiUserChatManager mucManager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
List<DiscoverItems.Item> answer = new ArrayList<>();
|
|
||||||
Iterator<Jid> rooms = mucManager.getJoinedRooms().iterator();
|
|
||||||
while (rooms.hasNext()) {
|
|
||||||
answer.add(new DiscoverItems.Item(rooms.next()));
|
|
||||||
}
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getNodeFeatures() {...}
|
|
||||||
public List<DiscoverInfo.Identity> getNodeIdentities() {...}
|
|
||||||
public List<ExtensionElement> getNodePacketExtensions() {...}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover items associated with an XMPP entity
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
In order to obtain information about a specific item you have to first
|
|
||||||
discover the items available in an XMPP entity.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Once you have your ServiceDiscoveryManager you will be able to discover items
|
|
||||||
associated with an XMPP entity. To discover the items of a given XMPP entity
|
|
||||||
send **discoverItems(entityID)** to your _**ServiceDiscoveryManager**_ where
|
|
||||||
entityID is the ID of the entity. The message **discoverItems(entityID)** will
|
|
||||||
answer an instance of _**DiscoverItems**_ that contains the discovered items.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to discover the items associated with an online
|
|
||||||
catalog service:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
|
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
||||||
// Get the items of a given XMPP entity
|
|
||||||
// This example gets the items associated with online catalog service
|
|
||||||
DiscoverItems discoItems = discoManager.discoverItems("plays.shakespeare.lit");
|
|
||||||
// Get the discovered items of the queried XMPP entity
|
|
||||||
Iterator it = discoItems.getItems().iterator();
|
|
||||||
// Display the items of the remote XMPP entity
|
|
||||||
while (it.hasNext()) {
|
|
||||||
DiscoverItems.Item item = (DiscoverItems.Item) it.next();
|
|
||||||
System.out.println(item.getEntityID());
|
|
||||||
System.out.println(item.getNode());
|
|
||||||
System.out.println(item.getName());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover information about an XMPP entity
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Once you have discovered the entity ID and name of an item, you may want to
|
|
||||||
find out more about the item. The information desired generally is of two
|
|
||||||
kinds: 1) The item's identity and 2) The features offered by the item.
|
|
||||||
|
|
||||||
This information helps you determine what actions are possible with regard to
|
|
||||||
this item (registration, search, join, etc.) as well as specific feature types
|
|
||||||
of interest, if any (e.g., for the purpose of feature negotiation).
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Once you have your ServiceDiscoveryManager you will be able to discover
|
|
||||||
information associated with an XMPP entity. To discover the information of a
|
|
||||||
given XMPP entity send **discoverInfo(entityID)** to your
|
|
||||||
_**ServiceDiscoveryManager**_ where entityID is the ID of the entity. The
|
|
||||||
message **discoverInfo(entityID)** will answer an instance of
|
|
||||||
_**DiscoverInfo**_ that contains the discovered information.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to discover the information of a conference
|
|
||||||
room:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
|
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
||||||
// Get the information of a given XMPP entity
|
|
||||||
// This example gets the information of a conference room
|
|
||||||
DiscoverInfo discoInfo = discoManager.discoverInfo("balconyscene@plays.shakespeare.lit");
|
|
||||||
// Get the discovered identities of the remote XMPP entity
|
|
||||||
Iterator it = discoInfo.getIdentities().iterator();
|
|
||||||
// Display the identities of the remote XMPP entity
|
|
||||||
while (it.hasNext()) {
|
|
||||||
DiscoverInfo.Identity identity = (DiscoverInfo.Identity) it.next();
|
|
||||||
System.out.println(identity.getName());
|
|
||||||
System.out.println(identity.getType());
|
|
||||||
System.out.println(identity.getCategory());
|
|
||||||
}
|
|
||||||
// Check if room is password protected
|
|
||||||
discoInfo.containsFeature("muc_passwordprotected");
|
|
||||||
```
|
|
||||||
|
|
||||||
Publish publicly available items
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Publish your entity items to some kind of persistent storage. This enables
|
|
||||||
other entities to query that entity using the disco#items namespace and
|
|
||||||
receive a result even when the entity being queried is not online (or
|
|
||||||
available).
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Once you have your ServiceDiscoveryManager you will be able to publish items
|
|
||||||
to some kind of persistent storage. To publish the items of a given XMPP
|
|
||||||
entity you have to first create an instance of _**DiscoverItems**_ and
|
|
||||||
configure it with the items to publish. Then you will have to send
|
|
||||||
**publishItems(Jid entityID, DiscoverItems discoverItems)** to your
|
|
||||||
_**ServiceDiscoveryManager**_ where entityID is the address of the XMPP entity
|
|
||||||
that will persist the items and discoverItems contains the items to publish.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to publish new items:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
|
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a DiscoverItems with the items to publish
|
|
||||||
DiscoverItems itemsToPublish = new DiscoverItems();
|
|
||||||
Jid jid = JidCreate.from("pubsub.shakespeare.lit");
|
|
||||||
DiscoverItems.Item itemToPublish = new DiscoverItems.Item(jid);
|
|
||||||
itemToPublish.setName("Avatar");
|
|
||||||
itemToPublish.setNode("romeo/avatar");
|
|
||||||
itemToPublish.setAction(DiscoverItems.Item.UPDATE_ACTION);
|
|
||||||
itemsToPublish.addItem(itemToPublish);
|
|
||||||
|
|
||||||
// Publish the new items by sending them to the server
|
|
||||||
Jid jid2 = JidCreate.from("host");
|
|
||||||
discoManager.publishItems(jid2, itemsToPublish);
|
|
||||||
```
|
|
|
@ -1,155 +0,0 @@
|
||||||
File Transfer
|
|
||||||
=============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
The file transfer extension allows the user to transmit and receive files.
|
|
||||||
|
|
||||||
* Send a file to another user
|
|
||||||
* Receiving a file from another user
|
|
||||||
* Monitoring the progress of a file transfer
|
|
||||||
|
|
||||||
**XEP related:** [XEP-95](http://www.xmpp.org/extensions/xep-0095.html) [XEP-96](http://www.xmpp.org/extensions/xep-0096.html) [XEP-65](http://www.xmpp.org/extensions/xep-0065.html) [XEP-47](http://www.xmpp.org/extensions/xep-0047.html)
|
|
||||||
|
|
||||||
Send a file to another user
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A user may wish to send a file to another user. The other user has the option
|
|
||||||
of accepting, rejecting, or ignoring the users request. Smack provides a
|
|
||||||
simple interface in order to enable the user to easily send a file.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to send a file you must first construct an instance of the
|
|
||||||
**_FileTransferManager_** class. In order to instantiate the manager
|
|
||||||
you should call _FileTransferManager.getInstanceFor(connection)_, where connection is an XMPPConnection instance.
|
|
||||||
|
|
||||||
Once you have your **_FileTransferManager_** you will need to create an
|
|
||||||
outgoing file transfer to send a file. The method to use on the
|
|
||||||
**_FileTransferManager_** is the **createOutgoingFileTransfer(userID)**
|
|
||||||
method. The userID you provide to this method is the fully-qualified jabber ID
|
|
||||||
of the user you wish to send the file to. A fully-qualified jabber ID consists
|
|
||||||
of a node, a domain, and a resource. The user must be connected to the
|
|
||||||
resource in order to be able to receive the file transfer.
|
|
||||||
|
|
||||||
Now that you have your **_OutgoingFileTransfer_** instance you will want to
|
|
||||||
send the file. The method to send a file is **sendFile(file, description)**.
|
|
||||||
The file you provide to this method should be a readable file on the local
|
|
||||||
file system, and the description is a short description of the file to help
|
|
||||||
the user decide whether or not they would like to recieve the file.
|
|
||||||
|
|
||||||
For information on monitoring the progress of a file transfer see the
|
|
||||||
monitoring progress section of this document.
|
|
||||||
|
|
||||||
Other means to send a file are also provided as part of the
|
|
||||||
**_OutgoingFileTransfer_**. Please consult the Javadoc for more information.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to send a file:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create the file transfer manager
|
|
||||||
FileTransferManager manager = FileTransferManager.getInstanceFor(connection);
|
|
||||||
// Create the outgoing file transfer
|
|
||||||
OutgoingFileTransfer transfer = manager.createOutgoingFileTransfer(entityFullJid);
|
|
||||||
// Send the file
|
|
||||||
transfer.sendFile(new File("shakespeare_complete_works.txt"), "You won't believe this!");
|
|
||||||
```
|
|
||||||
|
|
||||||
Receiving a file from another user
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
The user may wish to receive files from another user. The process of receiving
|
|
||||||
a file is event driven, new file transfer requests are received from other
|
|
||||||
users via a listener registered with the file transfer manager.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to receive a file you must first construct an instance of the
|
|
||||||
**_FileTransferManager_** class. This class has one static factory method with one
|
|
||||||
parameter which is your XMPPConnection. In order to instantiate the manager
|
|
||||||
you should call _FileTransferManager.getInstanceFor(connection)_.
|
|
||||||
|
|
||||||
Once you have your **_FileTransferManager_** you will need to register a
|
|
||||||
listener with it. The FileTransferListener interface has one method,
|
|
||||||
**fileTransferRequest(request)**. When a request is received through this
|
|
||||||
method, you can either accept or reject the request. To help you make your
|
|
||||||
decision there are several methods in the **_FileTransferRequest_** class that
|
|
||||||
return information about the transfer request.
|
|
||||||
|
|
||||||
To accept the file transfer, call the **accept()** method. This method will create an
|
|
||||||
**_IncomingFileTransfer_**. After you have the file transfer you may start to
|
|
||||||
transfer the file by calling the **recieveFile(file)** method. The file
|
|
||||||
provided to this method will be where the data from the file transfer is saved.
|
|
||||||
|
|
||||||
Finally, to reject the file transfer the only method you need to call is
|
|
||||||
**reject()** on the **_FileTransferRequest_**.
|
|
||||||
|
|
||||||
For information on monitoring the progress of a file transfer see the
|
|
||||||
monitoring progress section of this document.
|
|
||||||
|
|
||||||
Other means to receive a file are also provided as part of the
|
|
||||||
**_IncomingFileTransfer_**. Please consult the Javadoc for more information.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to approve or reject a file transfer request:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create the file transfer manager
|
|
||||||
final FileTransferManager manager = FileTransferManager.getInstanceFor(connection);
|
|
||||||
// Create the listener
|
|
||||||
manager.addFileTransferListener(new FileTransferListener() {
|
|
||||||
public void fileTransferRequest(FileTransferRequest request) {
|
|
||||||
// Check to see if the request should be accepted
|
|
||||||
if (shouldAccept(request)) {
|
|
||||||
// Accept it
|
|
||||||
IncomingFileTransfer transfer = request.accept();
|
|
||||||
transfer.recieveFile(new File("shakespeare_complete_works.txt"));
|
|
||||||
} else {
|
|
||||||
// Reject it
|
|
||||||
request.reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Monitoring the progress of a file transfer
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
While a file transfer is in progress you may wish to monitor the progress of a
|
|
||||||
file transfer.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Both the **_IncomingFileTransfer_** and the **_OutgoingFileTransfer_** extend
|
|
||||||
the **_FileTransfer_** class which provides several methods to monitor how a
|
|
||||||
file transfer is progressing:
|
|
||||||
|
|
||||||
* **getStatus()** - The file transfer can be in several states, negotiating, rejected, cancelled, in progress, error, and complete. This method will return which state the file transfer is currently in.
|
|
||||||
* **getProgress()** - If the status of the file transfer is in progress this method will return a number between 0 and 1, 0 being the transfer has not yet started and 1 being the transfer is complete. It may also return a -1 if the transfer is not in progress.
|
|
||||||
* **isDone()** - Similar to getProgress() except it returns a _boolean_. If the state is rejected, canceled, error, or complete then true will be returned and false otherwise.
|
|
||||||
* **getError()** - If there is an error during the file transfer this method will return the type of error that occured.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to monitor a file transfer:
|
|
||||||
|
|
||||||
```
|
|
||||||
while(!transfer.isDone()) {
|
|
||||||
if (transfer.getStatus().equals(Status.error)) {
|
|
||||||
System.out.println("ERROR!!! " + transfer.getError());
|
|
||||||
} else {
|
|
||||||
System.out.println(transfer.getStatus());
|
|
||||||
System.out.println(transfer.getProgress());
|
|
||||||
}
|
|
||||||
sleep(1000);
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,114 +0,0 @@
|
||||||
HTTP over XMPP transport
|
|
||||||
========================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows the transport of HTTP communication over XMPP peer-to-peer networks.
|
|
||||||
|
|
||||||
* Discover HOXT support
|
|
||||||
* IQ exchange
|
|
||||||
|
|
||||||
|
|
||||||
Discover HOXT support
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Before using this extension you must ensure that your counterpart supports it
|
|
||||||
also.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Once you have your _**ServiceDiscoveryManager**_ you will be able to discover
|
|
||||||
information associated with an XMPP entity. To discover the information of a
|
|
||||||
given XMPP entity send **discoverInfo(entityID)** to your
|
|
||||||
_**ServiceDiscoveryManager**_ where entityID is the ID of the entity. The
|
|
||||||
message **discoverInfo(entityID)** will answer with an instance of
|
|
||||||
_**DiscoverInfo**_ that contains the discovered information.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to check if the counterpart supports HOXT:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
|
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
||||||
// Get the information of a given XMPP entity, where entityID is a Jid
|
|
||||||
DiscoverInfo discoInfo = discoManager.discoverInfo(entityID);
|
|
||||||
// Check if room is HOXT is supported
|
|
||||||
boolean isSupported = discoInfo.containsFeature("urn:xmpp:http");
|
|
||||||
```
|
|
||||||
IQ exchange
|
|
||||||
-----------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
You can use IQ's to perform HTTP requests and responses. This is applicable to
|
|
||||||
relatively short requests and responses (due to the limitation of XMPP message
|
|
||||||
size).
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
First you need to register a _**StanzaListener**_ to be able to handle
|
|
||||||
intended IQs.
|
|
||||||
|
|
||||||
For the HTTP client you:
|
|
||||||
|
|
||||||
* You create and send _**HttpOverXmppReq**_ request.
|
|
||||||
* Then you handle the _**HttpOverXmppResp**_ response in your _**StanzaListener**_.
|
|
||||||
|
|
||||||
For the HTTP server you:
|
|
||||||
|
|
||||||
* You handle the _**HttpOverXmppReq**_ requests in your _**StanzaListener**_.
|
|
||||||
* And create and send _**HttpOverXmppResp**_ responses.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we are an HTTP client, so we send a request (POST) and handle the
|
|
||||||
response:
|
|
||||||
|
|
||||||
```
|
|
||||||
// create a request body
|
|
||||||
String urlEncodedMessage = "I_love_you";
|
|
||||||
|
|
||||||
// prepare headers
|
|
||||||
List<Header> headers = new ArrayList<>();
|
|
||||||
headers.add(new Header("Host", "juliet.capulet.com"));
|
|
||||||
headers.add(new Header("Content-Type", "application/x-www-form-urlencoded"));
|
|
||||||
headers.add(new Header("Content-Length", Integer.toString(urlEncodedMessage.length())));
|
|
||||||
|
|
||||||
// provide body or request (not mandatory, - empty body is used for GET)
|
|
||||||
AbstractHttpOverXmpp.Text child = new AbstractHttpOverXmpp.Text(urlEncodedMessage);
|
|
||||||
AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child);
|
|
||||||
|
|
||||||
// create request
|
|
||||||
HttpOverXmppReq req = HttpOverXmppReq.buider()
|
|
||||||
.setMethod(HttpMethod.POST)
|
|
||||||
.setResource("/mailbox")
|
|
||||||
.setHeaders(headers)
|
|
||||||
.setVersion("1.1")
|
|
||||||
.setData(data)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// add to, where jid is the Jid of the individual the packet is sent to
|
|
||||||
req.setTo(jid);
|
|
||||||
|
|
||||||
// send it
|
|
||||||
connection.sendIqWithResponseCallback(req, new StanzaListener() {
|
|
||||||
public void processStanza(Stanza iq) {
|
|
||||||
HttpOverXmppResp resp = (HttpOverXmppResp) iq;
|
|
||||||
// check HTTP response code
|
|
||||||
if (resp.getStatusCode() == 200) {
|
|
||||||
// get content of the response
|
|
||||||
NamedElement child = resp.getData().getChild();
|
|
||||||
// check which type of content of the response arrived
|
|
||||||
if (child instanceof AbstractHttpOverXmpp.Xml) {
|
|
||||||
// print the message and anxiously read if from the console ;)
|
|
||||||
System.out.println(((AbstractHttpOverXmpp.Xml) child).getText());
|
|
||||||
} else {
|
|
||||||
// process other AbstractHttpOverXmpp data child subtypes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
|
@ -1,147 +0,0 @@
|
||||||
Smack Extensions User Manual
|
|
||||||
============================
|
|
||||||
|
|
||||||
The XMPP protocol includes a base protocol and many optional extensions
|
|
||||||
typically documented as "XEP's". Smack provides the org.jivesoftware.smack
|
|
||||||
package for the core XMPP protocol, and the org.jivesoftware.smackx package
|
|
||||||
for many of the protocol extensions.
|
|
||||||
|
|
||||||
This manual provides details about each of the "smackx" extensions, including
|
|
||||||
what it is, how to use it, and some simple example code.
|
|
||||||
|
|
||||||
Currently supported XEPs of Smack (all sub-projects)
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
||||||
| Discovering Alternative XMPP Connection Methods | [XEP-0156](https://xmpp.org/extensions/xep-0156.html) | 1.3.0 | Defines ways to discover alternative connection methods. |
|
|
||||||
| Nonzas | [XEP-0360](https://xmpp.org/extensions/xep-0360.html) | n/a | Defines the term "Nonza", describing every top level stream element that is not a Stanza. |
|
|
||||||
|
|
||||||
Currently supported XEPs of smack-tcp
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
||||||
| [Stream Management](streammanagement.md) | [XEP-0198](https://xmpp.org/extensions/xep-0198.html) | n/a | Allows active management of an XML Stream between two XMPP entities (stanza acknowledgement, stream resumption). |
|
|
||||||
|
|
||||||
Currently supported XEPs of smack-im
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|-----------------------------------|
|
|
||||||
| Roster Versioning | [XEP-0237](https://xmpp.org/extensions/xep-0237.html) | n/a | Efficient roster synchronization. |
|
|
||||||
|
|
||||||
Smack Extensions and currently supported XEPs of smack-extensions
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
||||||
| [Data Forms](dataforms.md) | [XEP-0004](https://xmpp.org/extensions/xep-0004.html) | n/a | Allows to gather data using Forms. |
|
|
||||||
| Last Activity | [XEP-0012](https://xmpp.org/extensions/xep-0012.html) | n/a | Communicating information about the last activity associated with an XMPP entity. |
|
|
||||||
| Flexible Offline Message Retrieval | [XEP-0013](https://xmpp.org/extensions/xep-0013.html) | n/a | Extension for flexible, POP3-like handling of offline messages. |
|
|
||||||
| [Privacy Lists](privacy.md) | [XEP-0016](https://xmpp.org/extensions/xep-0016.html) | n/a | Enabling or disabling communication with other entities. |
|
|
||||||
| [Service Discovery](disco.md) | [XEP-0030](https://xmpp.org/extensions/xep-0030.html) | n/a | Allows to discover services in XMPP entities. |
|
|
||||||
| Extended Stanza Addressing | [XEP-0033](https://xmpp.org/extensions/xep-0033.html) | n/a | Allows to include headers in stanzas in order to specifiy multiple recipients or sub-addresses. |
|
|
||||||
| [Multi User Chat](muc.md) | [XEP-0045](https://xmpp.org/extensions/xep-0045.html) | n/a | Allows configuration of, participation in, and administration of individual text-based conference rooms. |
|
|
||||||
| In-Band Bytestreams | [XEP-0047](https://xmpp.org/extensions/xep-0047.html) | n/a | Enables any two entities to establish a one-to-one bytestream between themselves using plain XMPP. |
|
|
||||||
| Bookmarks | [XEP-0048](https://xmpp.org/extensions/xep-0048.html) | n/a | Bookmarks, for e.g. MUC and web pages. |
|
|
||||||
| [Private Data](privatedata.md) | [XEP-0049](https://xmpp.org/extensions/xep-0049.html) | n/a | Manages private data. |
|
|
||||||
| Ad-Hoc Commands | [XEP-0050](https://xmpp.org/extensions/xep-0050.html) | n/a | Advertising and executing application-specific commands. |
|
|
||||||
| vcard-temp | [XEP-0054](https://xmpp.org/extensions/xep-0054.html) | n/a | The vCard-XML format currently in use. |
|
|
||||||
| Jabber Search | [XEP-0055](https://xmpp.org/extensions/xep-0055.html) | n/a | Search information repositories on the XMPP network. |
|
|
||||||
| Result Set Management | [XEP-0059](https://xmpp.org/extensions/xep-0059.html) | n/a | Page through and otherwise manage the receipt of large result sets |
|
|
||||||
| [PubSub](pubsub.md) | [XEP-0060](https://xmpp.org/extensions/xep-0060.html) | n/a | Generic publish and subscribe functionality. |
|
|
||||||
| SOCKS5 Bytestreams | [XEP-0065](https://xmpp.org/extensions/xep-0065.html) | n/a | Out-of-band bytestream between any two XMPP entities. |
|
|
||||||
| Field Standardization for Data Forms | [XEP-0068](https://xmpp.org/extensions/xep-0068.html) | n/a | Standardized field variables used in the context of jabber:x:data forms. |
|
|
||||||
| [XHTML-IM](xhtml.md) | [XEP-0071](https://xmpp.org/extensions/xep-0071.html) | n/a | Allows send and receiving formatted messages using XHTML. |
|
|
||||||
| In-Band Registration | [XEP-0077](https://xmpp.org/extensions/xep-0077.html) | n/a | In-band registration with XMPP services. |
|
|
||||||
| Advanced Message Processing | [XEP-0079](https://xmpp.org/extensions/xep-0079.html) | n/a | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. |
|
|
||||||
| User Location | [XEP-0080](https://xmpp.org/extensions/xep-0080.html) | n/a | Enabled communicating information about the current geographical or physical location of an entity. |
|
|
||||||
| XMPP Date Time Profiles | [XEP-0082](https://xmpp.org/extensions/xep-0082.html) | n/a | Standardization of Date and Time representation in XMPP. |
|
|
||||||
| Chat State Notifications | [XEP-0085](https://xmpp.org/extensions/xep-0085.html) | n/a | Communicating the status of a user in a chat session. |
|
|
||||||
| [Time Exchange](time.md) | [XEP-0090](https://xmpp.org/extensions/xep-0090.html) | n/a | Allows local time information to be shared between users. |
|
|
||||||
| Software Version | [XEP-0092](https://xmpp.org/extensions/xep-0092.html) | n/a | Retrieve and announce the software application of an XMPP entity. |
|
|
||||||
| Stream Initiation | [XEP-0095](https://xmpp.org/extensions/xep-0095.html) | n/a | Initiating a data stream between any two XMPP entities. |
|
|
||||||
| [SI File Transfer](filetransfer.md) | [XEP-0096](https://xmpp.org/extensions/xep-0096.html) | n/a | Transfer files between two users over XMPP. |
|
|
||||||
| User Mood | [XEP-0107](https://xmpp.org/extensions/xep-0107.html) | 1.2.1 | Communicate the users current mood. |
|
|
||||||
| [Entity Capabilities](caps.md) | [XEP-0115](https://xmpp.org/extensions/xep-0115.html) | n/a | Broadcasting and dynamic discovery of entity capabilities. |
|
|
||||||
| User Tune | [XEP-0118](https://xmpp.org/extensions/xep-0118.html) | n/a | Defines a payload format for communicating information about music to which a user is listening. |
|
|
||||||
| Data Forms Validation | [XEP-0122](https://xmpp.org/extensions/xep-0122.html) | n/a | Enables an application to specify additional validation guidelines . |
|
|
||||||
| Stanza Headers and Internet Metadata (SHIM) | [XEP-0131](https://xmpp.org/extensions/xep-0131.html) | 1.2 | Add Metadata Headers to Stanzas. |
|
|
||||||
| Service Administration | [XEP-0133](https://xmpp.org/extensions/xep-0133.html) | n/a | Recommended best practices for service-level administration of servers and components using Ad-Hoc Commands. |
|
|
||||||
| Stream Compression | [XEP-0138](https://xmpp.org/extensions/xep-0138.html) | n/a | Support for optional compression of the XMPP stream.
|
|
||||||
| Data Forms Layout | [XEP-0141](https://xmpp.org/extensions/xep-0141.html) | n/a | Enables an application to specify form layouts. |
|
|
||||||
| Personal Eventing Protocol | [XEP-0163](https://xmpp.org/extensions/xep-0163.html) | n/a | Using the XMPP publish-subscribe protocol to broadcast state change events associated with an XMPP account. |
|
|
||||||
| [Jingle](jingle.html) | [XEP-0166](https://xmpp.org/extensions/xep-0166.html) | n/a | Initiate and manage sessions between two XMPP entities. |
|
|
||||||
| User Nickname | [XEP-0172](https://xmpp.org/extensions/xep-0172.html) | n/a | Communicate user nicknames. |
|
|
||||||
| Message Delivery Receipts | [XEP-0184](https://xmpp.org/extensions/xep-0184.html) | n/a | Extension for message delivery receipts. The sender can request notification that the message has been delivered. |
|
|
||||||
| [Blocking Command](blockingcommand.md) | [XEP-0191](https://xmpp.org/extensions/xep-0191.html) | n/a | Communications blocking that is intended to be simpler than privacy lists (XEP-0016). |
|
|
||||||
| XMPP Ping | [XEP-0199](https://xmpp.org/extensions/xep-0199.html) | n/a | Sending application-level pings over XML streams.
|
|
||||||
| Entity Time | [XEP-0202](https://xmpp.org/extensions/xep-0202.html) | n/a | Allows entities to communicate their local time |
|
|
||||||
| Delayed Delivery | [XEP-0203](https://xmpp.org/extensions/xep-0203.html) | n/a | Extension for communicating the fact that an XML stanza has been delivered with a delay. |
|
|
||||||
| XMPP Over BOSH | [XEP-0206](https://xmpp.org/extensions/xep-0206.html) | n/a | Use Bidirectional-streams Over Synchronous HTTP (BOSH) to transport XMPP stanzas. |
|
|
||||||
| Data Forms Media Element | [XEP-0221](https://xmpp.org/extensions/xep-0221.html) | n/a | Allows to include media data in XEP-0004 data forms. |
|
|
||||||
| Attention | [XEP-0224](https://xmpp.org/extensions/xep-0224.html) | n/a | Getting attention of another user. |
|
|
||||||
| Bits of Binary | [XEP-0231](https://xmpp.org/extensions/xep-0231.html) | n/a | Including or referring to small bits of binary data in an XML stanza. |
|
|
||||||
| Software Information | [XEP-0232](https://xmpp.org/extensions/xep-0232.html) | 0.3 | Allows an entity to provide detailed data about itself in Service Discovery responses. |
|
|
||||||
| Best Practices for Resource Locking | [XEP-0296](https://xmpp.org/extensions/xep-0296.html) | n/a | Specifies best practices to be followed by Jabber/XMPP clients about when to lock into, and unlock away from, resources. |
|
|
||||||
| Stanza Forwarding | [XEP-0297](https://xmpp.org/extensions/xep-0297.html) | n/a | Allows forwarding of Stanzas. |
|
|
||||||
| Last Message Correction | [XEP-0308](https://xmpp.org/extensions/xep-0308.html) | n/a | Provides a method for indicating that a message is a correction of the last sent message. |
|
|
||||||
| Last User Interaction in Presence | [XEP-0319](https://xmpp.org/extensions/xep-0319.html) | n/a | Communicate time of last user interaction via XMPP presence notifications. |
|
|
||||||
| Data Forms Geolocation Element | [XEP-0350](https://xmpp.org/extensions/xep-0350.html) | n/a | Allows to include XEP-0080 gelocation data in XEP-0004 data forms. |
|
|
||||||
| [Group Chat Invitations](invitation.md) | n/a | n/a | Send invitations to other users to join a group chat room. |
|
|
||||||
| [Jive Properties](properties.md) | n/a | n/a | TODO |
|
|
||||||
|
|
||||||
|
|
||||||
Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|-----------------------------------------------------------|--------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| Message Carbons | [XEP-0280](https://xmpp.org/extensions/xep-0280.html) | n/a | Keep all IM clients for a user engaged in a conversation, by carbon-copy outbound messages to all interested resources. |
|
|
||||||
| [Message Archive Management](mam.md) | [XEP-0313](https://xmpp.org/extensions/xep-0313.html) | n/a | Query and control an archive of messages stored on a server. |
|
|
||||||
| Data Forms XML Element | [XEP-0315](https://xmpp.org/extensions/xep-0315.html) | n/a | Allows to include XML-data in XEP-0004 data forms. |
|
|
||||||
| [Internet of Things - Sensor Data](iot.md) | [XEP-0323](https://xmpp.org/extensions/xep-0323.html) | n/a | Sensor data interchange over XMPP. |
|
|
||||||
| [Internet of Things - Provisioning](iot.md) | [XEP-0324](https://xmpp.org/extensions/xep-0324.html) | n/a | Provisioning, access rights and user privileges for the Internet of Things. |
|
|
||||||
| [Internet of Things - Control](iot.md) | [XEP-0325](https://xmpp.org/extensions/xep-0325.html) | n/a | Describes how to control devices or actuators in an XMPP-based sensor network. |
|
|
||||||
| Jid Prep | [XEP-0328](https://xmpp.org/extensions/xep-0328.html) | 0.1 | Describes a way for an XMPP client to request an XMPP server to prep and normalize a given JID. |
|
|
||||||
| [HTTP over XMPP transport](hoxt.md) | [XEP-0332](https://xmpp.org/extensions/xep-0332.html) | n/a | Allows to transport HTTP communication over XMPP peer-to-peer networks. |
|
|
||||||
| Chat Markers | [XEP-0333](https://xmpp.org/extensions/xep-0333.html) | n/a | A solution of marking the last received, displayed and acknowledged message in a chat. |
|
|
||||||
| Message Processing Hints | [XEP-0334](https://xmpp.org/extensions/xep-0334.html) | n/a | Hints to entities routing or receiving a message. |
|
|
||||||
| JSON Containers | [XEP-0335](https://xmpp.org/extensions/xep-0335.html) | n/a | Encapsulation of JSON data within XMPP Stanzas. |
|
|
||||||
| [Internet of Things - Discovery](iot.md) | [XEP-0347](https://xmpp.org/extensions/xep-0347.html) | n/a | Describes how Things can be installed and discovered by their owners. |
|
|
||||||
| Client State Indication | [XEP-0352](https://xmpp.org/extensions/xep-0352.html) | n/a | A way for the client to indicate its active/inactive state. |
|
|
||||||
| [Push Notifications](pushnotifications.md) | [XEP-0357](https://xmpp.org/extensions/xep-0357.html) | n/a | Defines a way to manage push notifications from an XMPP Server. |
|
|
||||||
| Stable and Unique Stanza IDs | [XEP-0359](https://xmpp.org/extensions/xep-0359.html) | 0.5.0 | This specification describes unique and stable IDs for messages. |
|
|
||||||
| HTTP File Upload | [XEP-0363](https://xmpp.org/extensions/xep-0363.html) | 0.3.1 | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
|
|
||||||
| References | [XEP-0372](https://xmpp.org/extensions/xep-0363.html) | 0.2.0 | Add references like mentions or external data to stanzas. |
|
|
||||||
| Explicit Message Encryption | [XEP-0380](https://xmpp.org/extensions/xep-0380.html) | 0.3.0 | Mark a message as explicitly encrypted. |
|
|
||||||
| [OpenPGP for XMPP](ox.md) | [XEP-0373](https://xmpp.org/extensions/xep-0373.html) | 0.3.2 | Utilize OpenPGP to exchange encrypted and signed content. |
|
|
||||||
| [OpenPGP for XMPP: Instant Messaging](ox-im.md) | [XEP-0374](https://xmpp.org/extensions/xep-0374.html) | 0.2.0 | OpenPGP encrypted Instant Messaging. |
|
|
||||||
| [Spoiler Messages](spoiler.md) | [XEP-0382](https://xmpp.org/extensions/xep-0382.html) | 0.2.0 | Indicate that the body of a message should be treated as a spoiler. |
|
|
||||||
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-0384](https://xmpp.org/extensions/xep-0384.html) | n/a | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
|
|
||||||
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.6.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
|
|
||||||
| [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. |
|
|
||||||
| DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. |
|
|
||||||
| Stanza Content Encryption | [XEP-0420](https://xmpp.org/extensions/xep-0420.html) | 0.3.0 | End-to-end encryption of arbitrary extension elements. Smack provides elements and providers to be used by encryption mechanisms. |
|
|
||||||
| Message Fastening | [XEP-0422](https://xmpp.org/extensions/xep-0422.html) | 0.1.1 | Mark payloads on a message to be logistically fastened to a previous message. |
|
|
||||||
| Message Retraction | [XEP-0424](https://xmpp.org/extensions/xep-0424.html) | 0.2.0 | Mark messages as retracted. |
|
|
||||||
| Fallback Indication | [XEP-0428](https://xmpp.org/extensions/xep-0428.html) | 0.1.0 | Declare body elements of a message as ignorable fallback for naive legacy clients. |
|
|
||||||
|
|
||||||
Unofficial XMPP Extensions
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
||||||
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](https://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | n/a | Multi-User Chats for mobile XMPP applications and specific environment. |
|
|
||||||
| Google GCM JSON payload | n/a | n/a | Semantically the same as XEP-0335: JSON Containers. |
|
|
||||||
|
|
||||||
Legacy Smack Extensions and currently supported XEPs of smack-legacy
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
If a XEP becomes 'Deprecated' or 'Obsolete' the code will be moved to the *smack-legacy* subproject.
|
|
||||||
|
|
||||||
| Name | XEP | Version | Description |
|
|
||||||
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
|
|
||||||
| [Message Events](messageevents.md) | [XEP-0022](https://xmpp.org/extensions/xep-0022.html) | n/a | Requests and responds to message events. |
|
|
||||||
| [Roster Item Exchange](rosterexchange.md) | [XEP-0093](https://xmpp.org/extensions/xep-0093.html) | n/a | Allows roster data to be shared between users. |
|
|
|
@ -1,99 +0,0 @@
|
||||||
Smack Extensions Manual
|
|
||||||
|
|
||||||
Current Extensions
|
|
||||||
|
|
||||||
**Name**
|
|
||||||
**XEP #**
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
[Private Data](privatedata.md)
|
|
||||||
|
|
||||||
[XEP-0049](http://www.xmpp.org/extensions/xep-0049.html)
|
|
||||||
|
|
||||||
Manages private data.
|
|
||||||
|
|
||||||
[XHTML Messages](xhtml.md)
|
|
||||||
|
|
||||||
[XEP-0071](http://www.xmpp.org/extensions/xep-0071.html)
|
|
||||||
|
|
||||||
Allows send and receiving formatted messages using XHTML.
|
|
||||||
|
|
||||||
[Message Events](messageevents.md)
|
|
||||||
|
|
||||||
[XEP-0022](http://www.xmpp.org/extensions/xep-0022.html)
|
|
||||||
|
|
||||||
Requests and responds to message events.
|
|
||||||
|
|
||||||
[Data Forms](dataforms.md)
|
|
||||||
|
|
||||||
[XEP-0004](http://www.xmpp.org/extensions/xep-0004.html)
|
|
||||||
|
|
||||||
Allows to gather data using Forms.
|
|
||||||
|
|
||||||
[Multi User Chat](muc.md)
|
|
||||||
|
|
||||||
[XEP-0045](http://www.xmpp.org/extensions/xep-0045.html)
|
|
||||||
|
|
||||||
Allows configuration of, participation in, and administration of individual
|
|
||||||
text-based conference rooms.
|
|
||||||
|
|
||||||
[Roster Item Exchange](rosterexchange.md)
|
|
||||||
|
|
||||||
[XEP-0093](http://www.xmpp.org/extensions/xep-0093.html)
|
|
||||||
|
|
||||||
Allows roster data to be shared between users.
|
|
||||||
|
|
||||||
[Time Exchange](time.md)
|
|
||||||
|
|
||||||
[XEP-0090](http://www.xmpp.org/extensions/xep-0090.html)
|
|
||||||
|
|
||||||
Allows local time information to be shared between users.
|
|
||||||
|
|
||||||
[Group Chat Invitations](invitation.md)
|
|
||||||
|
|
||||||
N/A
|
|
||||||
|
|
||||||
Send invitations to other users to join a group chat room.
|
|
||||||
|
|
||||||
[Service Discovery](disco.md)
|
|
||||||
|
|
||||||
[XEP-0030](http://www.xmpp.org/extensions/xep-0030.html)
|
|
||||||
|
|
||||||
Allows to discover services in XMPP entities.
|
|
||||||
|
|
||||||
[File Transfer](filetransfer.md)
|
|
||||||
|
|
||||||
[XEP-0096](http://www.xmpp.org/extensions/xep-0096.html)
|
|
||||||
|
|
||||||
Transfer files between two users over XMPP.
|
|
||||||
|
|
||||||
[PubSub](pubsub.md)
|
|
||||||
|
|
||||||
[XEP-0060](http://www.xmpp.org/extensions/xep-0060.html)
|
|
||||||
|
|
||||||
Generic publish and subscribe functionality.
|
|
||||||
|
|
||||||
[Entity Capabilities](caps.md)
|
|
||||||
|
|
||||||
[XEP-0115](http://www.xmpp.org/extensions/xep-0115.html)
|
|
||||||
|
|
||||||
Broadcasting and dynamic discovery of entity capabilities.
|
|
||||||
|
|
||||||
[Privacy Lists](privacy.md)
|
|
||||||
|
|
||||||
[XEP-0016](http://www.xmpp.org/extensions/xep-0016.html)
|
|
||||||
|
|
||||||
Enabling or disabling communication with other entities.
|
|
||||||
|
|
||||||
[HTTP over XMPP transport](hoxt.md)
|
|
||||||
|
|
||||||
[XEP-0332](http://www.xmpp.org/extensions/xep-0332.html)
|
|
||||||
|
|
||||||
Allows to transport HTTP communication over XMPP peer-to-peer networks.
|
|
||||||
|
|
||||||
[Jive Properties](properties.md)
|
|
||||||
|
|
||||||
N/A
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
Group Chat Invitations
|
|
||||||
======================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
The group chat invitation extension is used to invite other users to a
|
|
||||||
group chat room.
|
|
||||||
|
|
||||||
* Inviting Other Users
|
|
||||||
* Listen for Invitations
|
|
||||||
|
|
||||||
**XEP related:** N/A -- this protocol is outdated now that the Multi-User Chat (MUC) XEP is available ([XEP-45](http://www.xmpp.org/extensions/xep-0045.html)). However, most existing clients still use this older protocol. Once MUC support becomes more widespread, this API may be deprecated.
|
|
||||||
|
|
||||||
Inviting Other Users
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
To use the GroupChatInvitation packet extension to invite another user to a
|
|
||||||
group chat room, address a new message to the user and set the room name
|
|
||||||
appropriately, as in the following code example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Message message = new Message("user@chat.example.com", "Join me for a group chat!");
|
|
||||||
message.addExtension(new GroupChatInvitation("room@chat.example.com"));
|
|
||||||
con.sendStanza(message);
|
|
||||||
```
|
|
||||||
|
|
||||||
The XML generated for the invitation portion of the code above would be:
|
|
||||||
|
|
||||||
```
|
|
||||||
<x xmlns="jabber:x:conference" jid="room@chat.example.com"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Listening for Invitations
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
To listen for group chat invitations, use a StanzaExtensionFilter for the `x`
|
|
||||||
element name and `jabber:x:conference` namespace, as in the following code
|
|
||||||
example:
|
|
||||||
|
|
||||||
```
|
|
||||||
StanzaFilter filter = new StanzaExtensionFilter("x", "jabber:x:conference");
|
|
||||||
// Create a packet collector or packet listeners using the filter...
|
|
||||||
```
|
|
|
@ -1,118 +0,0 @@
|
||||||
Internet of Things (XEP-0323, -0324, -0325, -0347)
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
The Internet of Things (IoT) XEPs are an experimental open standard how XMPP can be used for IoT. They currently consists of
|
|
||||||
- XEP-0323 Sensor Data
|
|
||||||
- XEP-0324 Provisioning
|
|
||||||
- XEP-0325 Control
|
|
||||||
- XEP-0326 Concentrators
|
|
||||||
- XEP-0347 Discovery
|
|
||||||
|
|
||||||
Smack only supports a subset of the functionality described by the XEPs!
|
|
||||||
|
|
||||||
Thing Builder
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The `org.jivesoftware.smackx.iot.Thing` class acts as basic entity representing a single "Thing" which can be used to retrieve data from or to send control commands to. `Things` are constructed using a builder API.
|
|
||||||
|
|
||||||
|
|
||||||
Reading data from things
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
For example, we can build a Thing which provides the current temperature with
|
|
||||||
|
|
||||||
```java
|
|
||||||
Thing dataThing = Thing.builder().setKey(key).setSerialNumber(sn).setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() {
|
|
||||||
@Override
|
|
||||||
public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) {
|
|
||||||
int temp = getCurrentTemperature();
|
|
||||||
IoTDataField.IntField field = new IntField("temperature", temp);
|
|
||||||
callback.momentaryReadOut(Collections.singletonList(field));
|
|
||||||
}
|
|
||||||
}).build();
|
|
||||||
```
|
|
||||||
|
|
||||||
While not strictly required, most things are identified via a key and serial number. We also build the thing with a "momentary read out request handler" which when triggered, retrieves the current temperature and reports it back to the requestor.
|
|
||||||
|
|
||||||
After the `Thing` is built, it needs to be made available so that other entities within the federated XMPP network can use it. Right now we only install the Thing in the `IoTDataManager`, which means the thing will act on read out requests but not be managed by a provisioning server.
|
|
||||||
|
|
||||||
```java
|
|
||||||
IoTDataManager iotDataManager = IoTDataManager.getInstanceFor(connection);
|
|
||||||
iotDataManager.installThing(thing);
|
|
||||||
```
|
|
||||||
|
|
||||||
The data can be read out also by using the `IoTDataManager`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
FullJid jid = …
|
|
||||||
List<IoTFieldsExtension> values = iotDataManager.requestMomentaryValuesReadOut(jid);
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you have to unwrap the `IoTDataField` instances from the `IoTFieldsExtension`. Note that Smack currently only supports a subset of the specified data types.
|
|
||||||
|
|
||||||
Controlling a thing
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Things can also be controlled, e.g. to turn on a light. Let's create a thing which can be used to turn the light on and off.
|
|
||||||
|
|
||||||
```java
|
|
||||||
Thing controlThing = Thing.builder().setKey(key).setSerialNumber(sn).setControlRequestHandler(new ThingControlRequest() {
|
|
||||||
@Override
|
|
||||||
public void processRequest(Jid from, Collection<SetData> setData) throws XMPPErrorException {
|
|
||||||
for (final SetData data : setData) {
|
|
||||||
if (!data.getName().equals("light")) continue;
|
|
||||||
if (!(data instanceof SetBoolData)) continue;
|
|
||||||
SetBoolData boolData = (SetBoolData) data;
|
|
||||||
setLight(boolData.getBooleanValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).build();
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we have to install this thing into the `IoTControlManager`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
IoTControlManager iotControlManager = IoTControlManager.getInstanceFor(connection);
|
|
||||||
iotControlManager.installThing(thing);
|
|
||||||
```
|
|
||||||
|
|
||||||
The `IoTControlManager` can also be used to control a thing:
|
|
||||||
|
|
||||||
```java
|
|
||||||
FullJid jid = …
|
|
||||||
SetData setData = new SetBoolData("light", true);
|
|
||||||
iotControlManager.setUsingIq(jid, setData);
|
|
||||||
```
|
|
||||||
|
|
||||||
Smack currently only supports a subset of the possible data types for set data.
|
|
||||||
|
|
||||||
Discovery
|
|
||||||
---------
|
|
||||||
|
|
||||||
You may have wondered how a full JIDs of things can be determined. One approach is using the discovery mechanisms specified in XEP-0347. Smack provides the `IoTDiscoveryManager` as an API for this.
|
|
||||||
|
|
||||||
For example, instead of just installing the previous things in the `IoTDataManager` and/or `IoTControlManager`, we could also use the `IoTDiscoveryManger` to register the thing with a registry. Doing this also installs the thing in the `IoTDataManager` and the `IoTControlManager`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
IoTDiscoveryManager iotDiscoveryManager = IoTDiscoveryManager.getInstanceFor(connection);
|
|
||||||
iotDiscovyerManager.registerThing(thing);
|
|
||||||
```
|
|
||||||
|
|
||||||
The registry will now make the thing known to a broader audience, and available for a potential owner.
|
|
||||||
|
|
||||||
The `IoTDiscoveryManager` can also be used to claim, disown, remove and unregister a thing.
|
|
||||||
|
|
||||||
Provisioning
|
|
||||||
------------
|
|
||||||
|
|
||||||
Things can usually only be used by other things if they are friends. Since a thing normally can't decide on its own if an incoming friendship request should be granted or not, we can delegate this decision to a provisioning service. Smack provides the `IoTProvisinoManager` to deal with friendship and provisioning.
|
|
||||||
|
|
||||||
For example, if you want to befriend another thing:
|
|
||||||
|
|
||||||
```java
|
|
||||||
BareJid jid = …
|
|
||||||
IoTProvisioningManager iotProvisioningManager = IoTProvisioningManager.getInstanceFor(connection);
|
|
||||||
iotProvisioningManager.sendFriendshipRequest(jid);
|
|
||||||
```
|
|
|
@ -1,72 +0,0 @@
|
||||||
Jingle
|
|
||||||
======
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0116: Jingle](http://xmpp.org/extensions/xep-0166.html)
|
|
||||||
|
|
||||||
Jingle Element Structure
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
jingle
|
|
||||||
│ action (REQUIRED, XEP-0166 § 7.2)
|
|
||||||
| content-accept
|
|
||||||
| content-add
|
|
||||||
| content-modify
|
|
||||||
| content-reject
|
|
||||||
| content-remove
|
|
||||||
| description-info
|
|
||||||
| security-info
|
|
||||||
| session-accept
|
|
||||||
| session-info
|
|
||||||
| session-initiate
|
|
||||||
| transport-accept
|
|
||||||
| transport-info
|
|
||||||
| transport-reject
|
|
||||||
| transport-replace
|
|
||||||
│ initator (RECOMMENDED for session initiate, NOT RECOMMENDED otherwise, full JID, XEP-0166 § 7.1)
|
|
||||||
│ responder (RECOMMENDED for session accept, NOT RECOMMENDED otherwise, full JID. XEP-0166 § 7.1)
|
|
||||||
│ sid (REQUIRED, SHOULD match XML Nmtoken production)
|
|
||||||
│
|
|
||||||
├── <reason/> (optional, XEP-0166 § 7.4)
|
|
||||||
│ │
|
|
||||||
│ └──(alternative─session│busy│..)
|
|
||||||
│
|
|
||||||
└── <content/> (one or more, XEP-0166 § 7.3)
|
|
||||||
│ creator (REQUIRED, must be one of)
|
|
||||||
| initiator
|
|
||||||
| responder
|
|
||||||
│ disposition (OPTIONAL)
|
|
||||||
│ name (REQUIRED)
|
|
||||||
│ senders (OPTIONAL, except when content-modify then REQUIRED)
|
|
||||||
| both (default)
|
|
||||||
| initiator
|
|
||||||
| none
|
|
||||||
| responder
|
|
||||||
│
|
|
||||||
├──description
|
|
||||||
│ │ media
|
|
||||||
│ │ xmlns
|
|
||||||
│ │
|
|
||||||
│ ├──payload─type
|
|
||||||
│ │
|
|
||||||
│ └──file (XEP─0234)
|
|
||||||
│
|
|
||||||
└──transport
|
|
||||||
│ xmlns
|
|
||||||
│ pwd (OPTIONAL, XEP-0176 Jingle ICE)
|
|
||||||
│ ufrag (OPTIONAL, XEP-0176 Jingle ICE)
|
|
||||||
│ mode (XEP-0234 Jingle File Transfer)
|
|
||||||
│ sid (XEP-0234 Jingle File Transfer)
|
|
||||||
│
|
|
||||||
└──candidate
|
|
||||||
component
|
|
||||||
foundation
|
|
||||||
generation
|
|
||||||
id
|
|
||||||
ip
|
|
||||||
network
|
|
||||||
port
|
|
||||||
priority
|
|
||||||
protocol
|
|
||||||
type
|
|
||||||
```
|
|
|
@ -1,6 +0,0 @@
|
||||||
Message Archive Management
|
|
||||||
==========================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
See the javadoc of `MamManager` for details.
|
|
|
@ -1,68 +0,0 @@
|
||||||
Message Markup
|
|
||||||
==============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
[Message Markup (XEP-0394)](https://xmpp.org/extensions/xep-0394.html) can be used as an alternative to XHTML-IM to style messages, while keeping the body and markup information strictly separated.
|
|
||||||
This implementation can *not* be used to render message bodies, but will offer a simple to use interface for creating ExtensionElements which encode the markup information.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
The most important class is the `MarkupElement` class, which contains a Builder.
|
|
||||||
|
|
||||||
To start creating a Message Markup Extension, call `MarkupElement.getBuilder()`.
|
|
||||||
(Almost) all method calls documented below will be made on the builder.
|
|
||||||
|
|
||||||
Whenever a method call receives a `start` and `end` index, `start` represents the first character, which is affected by the styling, while `end` is the character *after* the last affected character.
|
|
||||||
|
|
||||||
### Inline styling
|
|
||||||
|
|
||||||
Currently there are 3 styles available:
|
|
||||||
* *emphasis*, which should be rendered by a client as *italic*, or **bold**
|
|
||||||
* *code*, which should be rendered in `monospace`
|
|
||||||
* *deleted*, which should be rendered as ~~strikethrough~~.
|
|
||||||
|
|
||||||
Those styles are available by calling `builder.setEmphasis(int start, int end)`,
|
|
||||||
`builder.setDeleted(int start, int end)` and `builder.setCode(int start, int end)`.
|
|
||||||
|
|
||||||
If you want to apply multiple inline styles to a section, you can do the following:
|
|
||||||
```
|
|
||||||
Set<SpanElement.SpanStyle> spanStyles = new HashSet<>();
|
|
||||||
styles.add(SpanElement.SpanStyle.emphasis);
|
|
||||||
styles.add(SpanElement.SpanStyle.deleted);
|
|
||||||
builder.addSpan(start, end, spanStyles);
|
|
||||||
```
|
|
||||||
|
|
||||||
Note, that spans cannot overlap one another.
|
|
||||||
|
|
||||||
### Block Level Styling
|
|
||||||
|
|
||||||
Available block level styles are:
|
|
||||||
* Code blocks, which should be rendered as
|
|
||||||
```
|
|
||||||
blocks
|
|
||||||
of
|
|
||||||
code
|
|
||||||
```
|
|
||||||
|
|
||||||
* Itemized lists, which should render as
|
|
||||||
* Lists
|
|
||||||
* with possibly multiple
|
|
||||||
* entries
|
|
||||||
|
|
||||||
* Block Quotes, which should be rendered by the client
|
|
||||||
> as quotes, which
|
|
||||||
>> also can be nested
|
|
||||||
|
|
||||||
To mark a section as code block, call `builder.setCodeBlock(start, end)`.
|
|
||||||
|
|
||||||
To create a list, call `MarkupElement.Builder.ListBuilder lbuilder = builder.beginList()`, which will return a list builder.
|
|
||||||
On this you can call `lbuilder.addEntry(start, end)` to add an entry.
|
|
||||||
|
|
||||||
Note: If you add an entry, the start value MUST be equal to the end value of the previous added entry!
|
|
||||||
|
|
||||||
To end the list, call `lbuilder.endList()`, which will return the MessageElement builder.
|
|
||||||
|
|
||||||
To create a block quote, call `builder.setBlockQuote(start, end)`.
|
|
||||||
|
|
||||||
Note that block level elements MUST NOT overlap each other boundaries, but may be fully contained (nested) within each other.
|
|
|
@ -1,650 +0,0 @@
|
||||||
Multi User Chat
|
|
||||||
===============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows configuration of, participation in, and administration of individual
|
|
||||||
text-based conference rooms.
|
|
||||||
|
|
||||||
* Create a new Room
|
|
||||||
* Join a room
|
|
||||||
* Manage room invitations
|
|
||||||
* Discover MUC support
|
|
||||||
* Discover joined rooms
|
|
||||||
* Discover room information
|
|
||||||
* Start a private chat
|
|
||||||
* Manage changes on room subject
|
|
||||||
* Manage role modifications
|
|
||||||
* Manage affiliation modifications
|
|
||||||
|
|
||||||
**XEP related:** [XEP-45](http://www.xmpp.org/extensions/xep-0045.html)
|
|
||||||
|
|
||||||
For all examples in this document, assume that the following variables exists:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Create the XMPP address (JID) of the MUC.
|
|
||||||
EntityBareJid mucJid = JidCreate.bareFrom("myroom@conference.jabber.org");
|
|
||||||
|
|
||||||
// Create the nickname.
|
|
||||||
Resourcepart nickname = Resourcepart.from("testbot");
|
|
||||||
|
|
||||||
// A other use (we may invite him to a MUC).
|
|
||||||
FullJid otherJid = JidCreate.fullFrom("user3@host.org/Smack");
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a new Room
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Allowed users may create new rooms. There are two types of rooms that you can
|
|
||||||
create. **Instant rooms** which are available for immediate access and are
|
|
||||||
automatically created based on some default configuration and **Reserved
|
|
||||||
rooms** which are manually configured by the room creator before anyone is
|
|
||||||
allowed to enter.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to create a room you will need to first create an instance of
|
|
||||||
_**MultiUserChat**_.
|
|
||||||
In order to do so, get a instance of `MultiUserChatManager` and call `getMultiUserChat(String)` to retrieve a `MultiUserChat` instance.
|
|
||||||
The next step is to send **create(String nickname)** to
|
|
||||||
the _**MultiUserChat**_ instance where nickname is the nickname to use when
|
|
||||||
joining the room.
|
|
||||||
|
|
||||||
Depending on the type of room that you want to create you will have to use
|
|
||||||
different configuration forms. In order to create an Instant room just use
|
|
||||||
`MucCreateConfigFormHandle.makeInstant()`. But if you
|
|
||||||
want to create a Reserved room then you should first get the room's
|
|
||||||
configuration form, complete the form and finally send it back to the server.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to create an instant room:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Get a MultiUserChat using MultiUserChatManager
|
|
||||||
MultiUserChat muc = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
// Create the room and send an empty configuration form to make this an instant room
|
|
||||||
muc.create(nickname).makeInstant();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to create a reserved room. The form is
|
|
||||||
completed with default values:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a MultiUserChat using an XMPPConnection for a room
|
|
||||||
MultiUserChat muc = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
// Prepare a list of owners of the new room
|
|
||||||
Set<Jid> owners = JidUtil.jidSetFrom(new String[] { "me@example.org", "juliet@example.org" });
|
|
||||||
|
|
||||||
// Create the room
|
|
||||||
muc.create(nickname)
|
|
||||||
.getConfigFormManger()
|
|
||||||
.setRoomOwners(owners)
|
|
||||||
.submitConfigurationForm();
|
|
||||||
```
|
|
||||||
|
|
||||||
Join a room
|
|
||||||
-----------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Your usual first step in order to send messages to a room is to join the room.
|
|
||||||
Multi User Chat allows to specify several parameter while joining a room.
|
|
||||||
Basically you can control the amount of history to receive after joining the
|
|
||||||
room as well as provide your nickname within the room and a password if the
|
|
||||||
room is password protected.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to join a room you will need to first get an instance of
|
|
||||||
_**MultiUserChat**_.
|
|
||||||
In order to do so, get a instance of `MultiUserChatManager` and call `getMultiUserChat(String)` to retrieve a `MultiUserChat` instance.
|
|
||||||
The next step is to send **join(...)** to the
|
|
||||||
_**MultiUserChat**_ instance. But first you will have to decide which join
|
|
||||||
message to send. If you want to just join the room without a password and
|
|
||||||
without specifying the amount of history to receive then you could use
|
|
||||||
**join(String nickname)** where nickname if your nickname in the room. In case
|
|
||||||
the room requires a password in order to join you could then use **join(String
|
|
||||||
nickname, String password)**. And finally, the most complete way to join a
|
|
||||||
room is to send **join(String nickname, String password, DiscussionHistory
|
|
||||||
history, long timeout)** where nickname is your nickname in the room, ,
|
|
||||||
password is your password to join the room, history is an object that
|
|
||||||
specifies the amount of history to receive and timeout is the milliseconds to
|
|
||||||
wait for a response from the server.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to join a room with a given nickname:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a MultiUserChat using an XMPPConnection for a room
|
|
||||||
MultiUserChat muc2 = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
// User2 joins the new room
|
|
||||||
// The room service will decide the amount of history to send
|
|
||||||
muc2.join(nickname);
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to join a room with a given nickname and
|
|
||||||
password:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a MultiUserChat using an XMPPConnection for a room
|
|
||||||
MultiUserChat muc2 = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
// User2 joins the new room using a password
|
|
||||||
// The room service will decide the amount of history to send
|
|
||||||
muc2.join(nickname, "password");
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to join a room with a given nickname specifying
|
|
||||||
the amount of history to receive:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a MultiUserChat using an XMPPConnection for a room
|
|
||||||
MultiUserChat muc2 = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
// User2 joins the new room using a password and specifying
|
|
||||||
// the amount of history to receive. In this example we are requesting the last 5 messages.
|
|
||||||
DiscussionHistory history = new DiscussionHistory();
|
|
||||||
history.setMaxStanzas(5);
|
|
||||||
muc2.join(nickname, "password", history, conn1.getPacketReplyTimeout());
|
|
||||||
```
|
|
||||||
|
|
||||||
Manage room invitations
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
It can be useful to invite another user to a room in which one is an occupant.
|
|
||||||
Depending on the room's type the invitee could receive a password to use to
|
|
||||||
join the room and/or be added to the member list if the room is of type
|
|
||||||
members-only. Smack allows to send room invitations and let potential invitees
|
|
||||||
to listening for room invitations and inviters to listen for invitees'
|
|
||||||
rejections.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to invite another user to a room you must be already joined to the
|
|
||||||
room. Once you are joined just send **invite(String participant, String
|
|
||||||
reason)** to the _**MultiUserChat**_ where participant is the user to invite
|
|
||||||
to the room (e.g. hecate@shakespeare.lit) and reason is the reason why the
|
|
||||||
user is being invited.
|
|
||||||
|
|
||||||
If potential invitees want to listen for room invitations then the invitee
|
|
||||||
must add an _**InvitationListener**_ to the _**MultiUserChatManager**_ class. Since
|
|
||||||
the _**InvitationListener**_ is an _interface_, it is necessary to create a
|
|
||||||
class that implements this _interface_. If an inviter wants to listen for room
|
|
||||||
invitation rejections, just add an _**InvitationRejectionListener**_ to the
|
|
||||||
_**MultiUserChat**_. _**InvitationRejectionListener**_ is also an interface so
|
|
||||||
you will need to create a class that implements this interface.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to invite another user to the room and lister
|
|
||||||
for possible rejections:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Create a MultiUserChat using an XMPPConnection for a room
|
|
||||||
MultiUserChat muc2 = manager.getMultiUserChat(mucJid);
|
|
||||||
|
|
||||||
muc2.join(nickname);
|
|
||||||
// User2 listens for invitation rejections
|
|
||||||
muc2.addInvitationRejectionListener(new InvitationRejectionListener() {
|
|
||||||
public void invitationDeclined(String invitee, String reason) {
|
|
||||||
// Do whatever you need here...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// User2 invites user3 to join to the room
|
|
||||||
muc2.invite(otherJid, "Meet me in this excellent room");
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to listen for room invitations and decline
|
|
||||||
invitations:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// User3 listens for MUC invitations
|
|
||||||
MultiUserChatManager.getInstanceFor(connection).addInvitationListener(new InvitationListener() {
|
|
||||||
public void invitationReceived(XMPPConnection conn, String room, EntityFullJid inviter, String reason, String password) {
|
|
||||||
// Reject the invitation
|
|
||||||
MultiUserChat.decline(conn, room, inviter.asBareJid()s, "I'm busy right now");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover MUC support
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A user may want to discover if one of the user's contacts supports the Multi-
|
|
||||||
User Chat protocol.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to discover if one of the user's contacts supports MUC just send
|
|
||||||
**isServiceEnabled(String user)** to the
|
|
||||||
_**MultiUserChatManager**_ class where user is a fully qualified XMPP ID, e.g.
|
|
||||||
jdoe@example.com. You will receive a boolean indicating whether the user
|
|
||||||
supports MUC or not.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to discover support of MUC:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Discover whether user3@host.org supports MUC or not
|
|
||||||
boolean supports = MultiUserChatManager.getInstanceFor(connection).isServiceEnabled(otherJid);
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover joined rooms
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A user may also want to query a contact regarding which rooms the contact is
|
|
||||||
in.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to get the rooms where a user is in just send
|
|
||||||
**getJoinedRooms(String user)** to the
|
|
||||||
_**MultiUserChatManager**_ class where user is a fully qualified XMPP ID, e.g.
|
|
||||||
jdoe@example.com. You will get an Iterator of Strings as an answer where each
|
|
||||||
String represents a room name.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to get the rooms where a user is in:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Get the rooms where user3@host.org has joined
|
|
||||||
List<String> joinedRooms = manager.getJoinedRooms("user3@host.org/Smack");
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover room information
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A user may need to discover information about a room without having to
|
|
||||||
actually join the room. The server will provide information only for public
|
|
||||||
rooms.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to discover information about a room just send
|
|
||||||
**getRoomInfo(String room)** to the
|
|
||||||
_**MultiUserChatManager**_ class where room is the XMPP ID of the room, e.g.
|
|
||||||
roomName@conference.myserver. You will get a RoomInfo object that contains the
|
|
||||||
discovered room information.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to discover information about a room:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Get the MultiUserChatManager
|
|
||||||
MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
|
|
||||||
|
|
||||||
// Discover information about the room roomName@conference.myserver
|
|
||||||
RoomInfo info = manager.getRoomInfo("roomName@conference.myserver");
|
|
||||||
System.out.println("Number of occupants:" + info.getOccupantsCount());
|
|
||||||
System.out.println("Room Subject:" + info.getSubject());
|
|
||||||
```
|
|
||||||
|
|
||||||
Start a private chat
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A room occupant may want to start a private chat with another room occupant
|
|
||||||
even though they don't know the fully qualified XMPP ID (e.g.
|
|
||||||
jdoe@example.com) of each other.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To create a private chat with another room occupant just send
|
|
||||||
**createPrivateChat(String participant)** to the _**MultiUserChat**_ that you
|
|
||||||
used to join the room. The parameter participant is the occupant unique room
|
|
||||||
JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). You will receive a regular
|
|
||||||
_**Chat**_ object that you can use to chat with the other room occupant.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to start a private chat with another room
|
|
||||||
occupant:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Start a private chat with another participant
|
|
||||||
Chat chat = muc2.createPrivateChat("myroom@conference.jabber.org/johndoe");
|
|
||||||
chat.sendMessage("Hello there");
|
|
||||||
```
|
|
||||||
|
|
||||||
Manage changes on room subject
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A common feature of multi-user chat rooms is the ability to change the subject
|
|
||||||
within the room. As a default, only users with a role of "moderator" are
|
|
||||||
allowed to change the subject in a room. Although some rooms may be configured
|
|
||||||
to allow a mere participant or even a visitor to change the subject.
|
|
||||||
|
|
||||||
Every time the room's subject is changed you may want to be notified of the
|
|
||||||
modification. The new subject could be used to display an in-room message.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to modify the room's subject just send **changeSubject(String
|
|
||||||
subject)** to the _**MultiUserChat**_ that you used to join the room where
|
|
||||||
subject is the new room's subject. On the other hand, if you want to be
|
|
||||||
notified whenever the room's subject is modified you should add a
|
|
||||||
_**SubjectUpdatedListener**_ to the _**MultiUserChat**_ by sending
|
|
||||||
**addSubjectUpdatedListener(SubjectUpdatedListener listener)** to the
|
|
||||||
_**MultiUserChat**_. Since the _**SubjectUpdatedListener**_ is an _interface_,
|
|
||||||
it is necessary to create a class that implements this _interface_.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to change the room's subject and react whenever
|
|
||||||
the room's subject is modified:
|
|
||||||
|
|
||||||
```
|
|
||||||
// An occupant wants to be notified every time the room's subject is changed
|
|
||||||
muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() {
|
|
||||||
public void subjectUpdated(String subject, String from) {
|
|
||||||
....
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// A room's owner changes the room's subject
|
|
||||||
muc2.changeSubject("New Subject");
|
|
||||||
```
|
|
||||||
|
|
||||||
Manage role modifications
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
There are four defined roles that an occupant can have:
|
|
||||||
|
|
||||||
1. Moderator
|
|
||||||
2. Participant
|
|
||||||
3. Visitor
|
|
||||||
4. None (the absence of a role)
|
|
||||||
|
|
||||||
These roles are temporary in that they do not persist across a user's visits
|
|
||||||
to the room and can change during the course of an occupant's visit to the
|
|
||||||
room.
|
|
||||||
|
|
||||||
A moderator is the most powerful occupant within the context of the room, and
|
|
||||||
can to some extent manage other occupants' roles in the room. A participant
|
|
||||||
has fewer privileges than a moderator, although he or she always has the right
|
|
||||||
to speak. A visitor is a more restricted role within the context of a
|
|
||||||
moderated room, since visitors are not allowed to send messages to all
|
|
||||||
occupants.
|
|
||||||
|
|
||||||
Roles are granted, revoked, and maintained based on the occupant's room
|
|
||||||
nickname or full JID. Whenever an occupant's role is changed Smack will
|
|
||||||
trigger specific events.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to grant voice (i.e. make someone a _participant_) just send the
|
|
||||||
message **grantVoice(String nickname)** to _**MultiUserChat**_. Use
|
|
||||||
**revokeVoice(String nickname)** to revoke the occupant's voice (i.e. make the
|
|
||||||
occupant a _visitor_).
|
|
||||||
|
|
||||||
In order to grant moderator privileges to a participant or visitor just send
|
|
||||||
the message **grantModerator(String nickname)** to _**MultiUserChat**_. Use
|
|
||||||
**revokeModerator(String nickname)** to revoke the moderator privilege from
|
|
||||||
the occupant thus making the occupant a participant.
|
|
||||||
|
|
||||||
Smack allows you to listen for role modification events. If you are interested
|
|
||||||
in listening role modification events of any occupant then use the listener
|
|
||||||
**_ParticipantStatusListener_**. But if you are interested in listening for
|
|
||||||
your own role modification events, use the listener **_UserStatusListener_**.
|
|
||||||
Both listeners should be added to the _**MultiUserChat**_ by using
|
|
||||||
**addParticipantStatusListener(ParticipantStatusListener listener)** or
|
|
||||||
**addUserStatusListener(UserStatusListener listener)** respectively. These
|
|
||||||
listeners include several notification events but you may be interested in
|
|
||||||
just a few of them. Smack provides default implementations for these listeners
|
|
||||||
avoiding you to implement all the interfaces' methods. The default
|
|
||||||
implementations are **_DefaultUserStatusListener_** and
|
|
||||||
**_DefaultParticipantStatusListener_**. Below you will find the sent messages
|
|
||||||
to the listeners whenever an occupant's role has changed.
|
|
||||||
|
|
||||||
These are the triggered events when the role has been upgraded:
|
|
||||||
|
|
||||||
| Old | New | Events |
|
|
||||||
|-----|-----|--------|
|
|
||||||
| None | Visitor | -- |
|
|
||||||
| Visitor | Participant | voiceGranted |
|
|
||||||
| Participant | Moderator | moderatorGranted |
|
|
||||||
| None | Participant | voiceGranted |
|
|
||||||
| None | Moderator | voiceGranted + moderatorGranted |
|
|
||||||
| Visitor | Moderator | voiceGranted + moderatorGranted |
|
|
||||||
|
|
||||||
These are the triggered events when the role has been downgraded:
|
|
||||||
|
|
||||||
| Old | New | Events |
|
|
||||||
|-----|-----|--------|
|
|
||||||
| Moderator | Participant | moderatorRevoked |
|
|
||||||
| Participant | Visitor | voiceRevoked |
|
|
||||||
| Visitor | None | kicked |
|
|
||||||
| Moderator | Visitor | voiceRevoked + moderatorRevoked |
|
|
||||||
| Moderator | None | kicked |
|
|
||||||
| Participant | None | kicked |
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to grant voice to a visitor and listen for the
|
|
||||||
notification events:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// User1 creates a room
|
|
||||||
muc = manager.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc.create("testbot");
|
|
||||||
// User1 (which is the room owner) configures the room as a moderated room
|
|
||||||
Form form = muc.getConfigurationForm();
|
|
||||||
FillableForm answerForm = configForm.getFillableForm();
|
|
||||||
answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
|
|
||||||
muc.sendConfigurationForm(answerForm);
|
|
||||||
|
|
||||||
// User2 joins the new room (as a visitor)
|
|
||||||
MultiUserChat muc2 = manager2.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc2.join("testbot2");
|
|
||||||
// User2 will listen for his own "voice" notification events
|
|
||||||
muc2.addUserStatusListener(new DefaultUserStatusListener() {
|
|
||||||
public void voiceGranted() {
|
|
||||||
super.voiceGranted();
|
|
||||||
...
|
|
||||||
}
|
|
||||||
public void voiceRevoked() {
|
|
||||||
super.voiceRevoked();
|
|
||||||
...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// User3 joins the new room (as a visitor)
|
|
||||||
MultiUserChat muc3 = manager3.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc3.join("testbot3");
|
|
||||||
// User3 will lister for other occupants "voice" notification events
|
|
||||||
muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
|
|
||||||
public void voiceGranted(String participant) {
|
|
||||||
super.voiceGranted(participant);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
public void voiceRevoked(String participant) {
|
|
||||||
super.voiceRevoked(participant);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The room's owner grants voice to user2
|
|
||||||
muc.grantVoice("testbot2");
|
|
||||||
```
|
|
||||||
|
|
||||||
Manage affiliation modifications
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
There are five defined affiliations that a user can have in relation to a
|
|
||||||
room:
|
|
||||||
|
|
||||||
1. Owner
|
|
||||||
2. Admin
|
|
||||||
3. Member
|
|
||||||
4. Outcast
|
|
||||||
5. None (the absence of an affiliation)
|
|
||||||
|
|
||||||
These affiliations are semi-permanent in that they persist across a user's
|
|
||||||
visits to the room and are not affected by happenings in the room.
|
|
||||||
Affiliations are granted, revoked, and maintained based on the user's bare
|
|
||||||
JID.
|
|
||||||
|
|
||||||
If a user without a defined affiliation enters a room, the user's affiliation
|
|
||||||
is defined as "none"; however, this affiliation does not persist across
|
|
||||||
visits.
|
|
||||||
|
|
||||||
Owners and admins are by definition immune from certain actions. Specifically,
|
|
||||||
an owner or admin cannot be kicked from a room and cannot be banned from a
|
|
||||||
room. An admin must first lose his or her affiliation (i.e., have an
|
|
||||||
affiliation of "none" or "member") before such actions could be performed on
|
|
||||||
them.
|
|
||||||
|
|
||||||
The member affiliation provides a way for a room owner or admin to specify a
|
|
||||||
"whitelist" of users who are allowed to enter a members-only room. When a
|
|
||||||
member enters a members-only room, his or her affiliation does not change, no
|
|
||||||
matter what his or her role is. The member affiliation also provides a way for
|
|
||||||
users to effectively register with an open room and thus be permanently
|
|
||||||
associated with that room in some way (one result may be that the user's
|
|
||||||
nickname is reserved in the room).
|
|
||||||
|
|
||||||
An outcast is a user who has been banned from a room and who is not allowed to
|
|
||||||
enter the room. Whenever a user's affiliation is changed Smack will trigger
|
|
||||||
specific events.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to grant membership to a room, administrator privileges or owner
|
|
||||||
priveliges just send **grantMembership(String jid)**, **grantAdmin(String
|
|
||||||
jid)** or **grantOwnership(String jid)** to _**MultiUserChat**_ respectively.
|
|
||||||
Use **revokeMembership(String jid)**, **revokeAdmin(String jid)** or
|
|
||||||
**revokeOwnership(String jid)** to revoke the membership to a room,
|
|
||||||
administrator privileges or owner priveliges respectively.
|
|
||||||
|
|
||||||
In order to ban a user from the room just send the message **banUser(String
|
|
||||||
jid, String reason)** to _**MultiUserChat**_.
|
|
||||||
|
|
||||||
Smack allows you to listen for affiliation modification events. If you are
|
|
||||||
interested in listening affiliation modification events of any user then use
|
|
||||||
the listener **_ParticipantStatusListener_**. But if you are interested in
|
|
||||||
listening for your own affiliation modification events, use the listener
|
|
||||||
**_UserStatusListener_**. Both listeners should be added to the
|
|
||||||
_**MultiUserChat**_ by using
|
|
||||||
**addParticipantStatusListener(ParticipantStatusListener listener)** or
|
|
||||||
**addUserStatusListener(UserStatusListener listener)** respectively. These
|
|
||||||
listeners include several notification events but you may be interested in
|
|
||||||
just a few of them. Smack provides default implementations for these listeners
|
|
||||||
avoiding you to implement all the interfaces' methods. The default
|
|
||||||
implementations are **_DefaultUserStatusListener_** and
|
|
||||||
**_DefaultParticipantStatusListener_**. Below you will find the sent messages
|
|
||||||
to the listeners whenever a user's affiliation has changed.
|
|
||||||
|
|
||||||
These are the triggered events when the affiliation has been upgraded:
|
|
||||||
|
|
||||||
| Old | New | Events |
|
|
||||||
|-----|-----|--------|
|
|
||||||
| None | Member | membershipGranted |
|
|
||||||
| Member | Admin | membershipRevoked + adminGranted |
|
|
||||||
| Admin | Owner | adminRevoked + ownershipGranted |
|
|
||||||
| None | Admin | adminGranted |
|
|
||||||
| None | Owner | ownershipGranted |
|
|
||||||
| Member | Owner | membershipRevoked + ownershipGranted |
|
|
||||||
|
|
||||||
These are the triggered events when the affiliation has been downgraded:
|
|
||||||
|
|
||||||
| Old | New | Events |
|
|
||||||
|-----|-----|--------|
|
|
||||||
| Owner | Admin | ownershipRevoked + adminGranted |
|
|
||||||
| Admin | Member | adminRevoked + membershipGranted |
|
|
||||||
| Member | None | membershipRevoked |
|
|
||||||
| Owner | Member | ownershipRevoked + membershipGranted |
|
|
||||||
| Owner | None | ownershipRevoked |
|
|
||||||
| Admin | None | adminRevoked |
|
|
||||||
| _Anyone_ | Outcast | banned |
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to grant admin privileges to a user and listen
|
|
||||||
for the notification events:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// User1 creates a room
|
|
||||||
muc = manager.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc.create("testbot");
|
|
||||||
// User1 (which is the room owner) configures the room as a moderated room
|
|
||||||
Form form = muc.getConfigurationForm();
|
|
||||||
FillableForm answerForm = configForm.getFillableForm();
|
|
||||||
answerForm.setAnswer("muc#roomconfig_moderatedroom", "1");
|
|
||||||
muc.sendConfigurationForm(answerForm);
|
|
||||||
|
|
||||||
// User2 joins the new room (as a visitor)
|
|
||||||
MultiUserChat muc2 = manager2.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc2.join("testbot2");
|
|
||||||
// User2 will listen for his own admin privileges
|
|
||||||
muc2.addUserStatusListener(new DefaultUserStatusListener() {
|
|
||||||
public void membershipRevoked() {
|
|
||||||
super.membershipRevoked();
|
|
||||||
...
|
|
||||||
}
|
|
||||||
public void adminGranted() {
|
|
||||||
super.adminGranted();
|
|
||||||
...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// User3 joins the new room (as a visitor)
|
|
||||||
MultiUserChat muc3 = manager3.getMultiUserChat("myroom@conference.jabber.org");
|
|
||||||
muc3.join("testbot3");
|
|
||||||
// User3 will lister for other users admin privileges
|
|
||||||
muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
|
|
||||||
public void membershipRevoked(String participant) {
|
|
||||||
super.membershipRevoked(participant);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
public void adminGranted(String participant) {
|
|
||||||
super.adminGranted(participant);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// The room's owner grants admin privileges to user2
|
|
||||||
muc.grantAdmin("user2@jabber.org");
|
|
||||||
```
|
|
|
@ -1,358 +0,0 @@
|
||||||
Multi-User Chat Light
|
|
||||||
=====================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows configuration of, participation in, and administration of presenceless Multi-User Chats.
|
|
||||||
Its feature set is a response to mobile XMPP applications needs and specific environment.
|
|
||||||
|
|
||||||
* Obtain the MUC Light Manager
|
|
||||||
* Obtain a MUC Light
|
|
||||||
* Create a new Room
|
|
||||||
* Destroy a room
|
|
||||||
* Leave a room
|
|
||||||
* Change room name
|
|
||||||
* Change room subject
|
|
||||||
* Set room configurations
|
|
||||||
* Manage changes on room name, subject and other configurations
|
|
||||||
* Get room information
|
|
||||||
* Manage room occupants
|
|
||||||
* Manage occupants modifications
|
|
||||||
* Discover MUC Light support
|
|
||||||
* Get occupied rooms
|
|
||||||
* Start a private chat
|
|
||||||
* Send message to a room
|
|
||||||
* Manage blocking list
|
|
||||||
|
|
||||||
**XEP related:** [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html)
|
|
||||||
|
|
||||||
|
|
||||||
Obtain the MUC Light Manager
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
MultiUserChatLightManager multiUserChatLightManager = MultiUserChatLightManager.getInstanceFor(connection);
|
|
||||||
```
|
|
||||||
|
|
||||||
Obtain a MUC Light
|
|
||||||
------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
MultiUserChatLight multiUserChatLight = multiUserChatLightManager.getMultiUserChatLight(roomJid);
|
|
||||||
```
|
|
||||||
`roomJid` is a EntityBareJid
|
|
||||||
|
|
||||||
|
|
||||||
Create a new room
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.create(roomName, occupants);
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```
|
|
||||||
multiUserChatLight.create(roomName, subject, customConfigs, occupants);
|
|
||||||
```
|
|
||||||
|
|
||||||
*roomName* is a `String`
|
|
||||||
|
|
||||||
*subject* is a `String`
|
|
||||||
|
|
||||||
*customConfigs* is a `HashMap<String, String>`
|
|
||||||
|
|
||||||
*occupants* is a `List<Jid>`
|
|
||||||
|
|
||||||
|
|
||||||
Destroy a room
|
|
||||||
---------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.destroy();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Leave a room
|
|
||||||
-------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.leave();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Change room name
|
|
||||||
----------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.changeRoomName(roomName);
|
|
||||||
```
|
|
||||||
*roomName* is a `String`
|
|
||||||
|
|
||||||
|
|
||||||
Change subject
|
|
||||||
--------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.changeSubject(subject);
|
|
||||||
```
|
|
||||||
*subject* is a `String`
|
|
||||||
|
|
||||||
|
|
||||||
Set room configurations
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.setRoomConfigs(customConfigs);
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```
|
|
||||||
multiUserChatLight.setRoomConfigs(roomName, customConfigs);
|
|
||||||
```
|
|
||||||
*customConfigs* is a `HashMap<String, String>` (which means [property name, value])
|
|
||||||
|
|
||||||
*roomName* is a `String`
|
|
||||||
|
|
||||||
|
|
||||||
Manage changes on room name, subject and other configurations
|
|
||||||
-------------------------------------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
// check if the message is because of a configurations change
|
|
||||||
if (message.hasExtension(MUCLightElements.ConfigurationsChangeExtension.ELEMENT, MUCLightElements.ConfigurationsChangeExtension.NAMESPACE)) {
|
|
||||||
|
|
||||||
// Get the configurations extension
|
|
||||||
MUCLightElements.ConfigurationsChangeExtension configurationsChangeExtension = MUCLightElements.ConfigurationsChangeExtension.from(message);
|
|
||||||
|
|
||||||
// Get new room name
|
|
||||||
String roomName = configurationsChangeExtension.getRoomName();
|
|
||||||
|
|
||||||
// Get new subject
|
|
||||||
String subject = configurationsChangeExtension.getSubject();
|
|
||||||
|
|
||||||
// Get new custom configurations
|
|
||||||
HashMap<String, String> customConfigs = configurationsChangeExtension.getCustomConfigs();
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Get room information
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
**Get configurations**
|
|
||||||
|
|
||||||
```
|
|
||||||
MUCLightRoomConfiguration configuration = multiUserChatLight.getConfiguration(version);
|
|
||||||
```
|
|
||||||
*version* is a `String`
|
|
||||||
|
|
||||||
or
|
|
||||||
```
|
|
||||||
MUCLightRoomConfiguration configuration = multiUserChatLight.getConfiguration();
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get room name
|
|
||||||
String roomName = configuration.getRoomName();
|
|
||||||
|
|
||||||
// Get subject
|
|
||||||
String subject = configuration.getSubject();
|
|
||||||
|
|
||||||
// Get custom configurations
|
|
||||||
HashMap<String, String> customConfigs = configuration.getCustomConfigs();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Get affiliations**
|
|
||||||
|
|
||||||
```
|
|
||||||
HashMap<Jid, MUCLightAffiliation> affiliations = multiUserChatLight.getAffiliations(version);
|
|
||||||
```
|
|
||||||
*version* is a `String`
|
|
||||||
|
|
||||||
or
|
|
||||||
```
|
|
||||||
HashMap<Jid, MUCLightAffiliation> affiliations = multiUserChatLight.getAffiliations();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Get full information**
|
|
||||||
|
|
||||||
```
|
|
||||||
MUCLightRoomInfo info = multiUserChatLight.getFullInfo(version);
|
|
||||||
```
|
|
||||||
*version* is a `String`
|
|
||||||
|
|
||||||
or
|
|
||||||
```
|
|
||||||
MUCLightRoomInfo info = multiUserChatLight.getFullInfo();
|
|
||||||
```
|
|
||||||
```
|
|
||||||
// Get version
|
|
||||||
String version = info.getVersion();
|
|
||||||
|
|
||||||
// Get room
|
|
||||||
Jid room = info.getRoom();
|
|
||||||
|
|
||||||
// Get configurations
|
|
||||||
MUCLightRoomConfiguration configuration = info.getConfiguration();
|
|
||||||
|
|
||||||
// Get occupants
|
|
||||||
HashMap<Jid, MUCLightAffiliation> occupants = info.getOccupants();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Manage room occupants
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
To change room occupants:
|
|
||||||
```
|
|
||||||
multiUserChatLight.changeAffiliations(affiliations);
|
|
||||||
```
|
|
||||||
*affiliations* is a `HashMap<Jid, MUCLightAffiliation>`
|
|
||||||
|
|
||||||
|
|
||||||
Manage occupants modifications
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
// check if the message is because of an affiliations change
|
|
||||||
if (message.hasExtension(MUCLightElements.AffiliationsChangeExtension.ELEMENT, MUCLightElements.AffiliationsChangeExtension.NAMESPACE)) {
|
|
||||||
|
|
||||||
// Get the affiliations change extension
|
|
||||||
MUCLightElements.AffiliationsChangeExtension affiliationsChangeExtension = MUCLightElements.AffiliationsChangeExtension.from(message);
|
|
||||||
|
|
||||||
// Get the new affiliations
|
|
||||||
HashMap<EntityJid, MUCLightAffiliation> affiliations = affiliationsChangeExtension.getAffiliations();
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Discover MUC Light support
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
**Check if MUC Light feature is supported by the server**
|
|
||||||
|
|
||||||
```
|
|
||||||
boolean isSupported = multiUserChatLightManager.isFeatureSupported(mucLightService);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
**Get MUC Light services domains**
|
|
||||||
|
|
||||||
```
|
|
||||||
List<DomainBareJid> domains = multiUserChatLightManager.getLocalServices();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Get occupied rooms
|
|
||||||
------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
List<Jid> occupiedRooms = multiUserChatLightManager.getOccupiedRooms(mucLightService);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
|
|
||||||
Start a private chat
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
Chat chat = multiUserChatLight.createPrivateChat(occupant, listener);
|
|
||||||
```
|
|
||||||
*occupant* is a `EntityJid`
|
|
||||||
|
|
||||||
*listener* is a `ChatMessageListener`
|
|
||||||
|
|
||||||
|
|
||||||
Send message to a room
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
**Create message for an specific MUC Light**
|
|
||||||
|
|
||||||
```
|
|
||||||
Message message = multiUserChatLight.createMessage();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Send a message to an specific MUC Light**
|
|
||||||
|
|
||||||
```
|
|
||||||
multiUserChatLight.sendMessage(message);
|
|
||||||
```
|
|
||||||
*message* is a `Message`
|
|
||||||
|
|
||||||
|
|
||||||
Manage blocking list
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
**Get blocked list**
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get users and rooms blocked
|
|
||||||
List<Jid> jids = multiUserChatLightManager.getUsersAndRoomsBlocked(mucLightService);
|
|
||||||
|
|
||||||
// Get rooms blocked
|
|
||||||
List<Jid> jids = multiUserChatLightManager.getRoomsBlocked(mucLightService);
|
|
||||||
|
|
||||||
// Get users blocked
|
|
||||||
List<Jid> jids = multiUserChatLightManager.getUsersBlocked(mucLightService);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
**Block rooms**
|
|
||||||
|
|
||||||
```
|
|
||||||
// Block one room
|
|
||||||
multiUserChatLightManager.blockRoom(mucLightService, roomJid);
|
|
||||||
|
|
||||||
// Block several rooms
|
|
||||||
multiUserChatLightManager.blockRooms(mucLightService, roomsJids);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
*roomJid* is a `Jid`
|
|
||||||
|
|
||||||
*roomsJids* is a `List<Jid>`
|
|
||||||
|
|
||||||
**Block users**
|
|
||||||
|
|
||||||
```
|
|
||||||
// Block one user
|
|
||||||
multiUserChatLightManager.blockUser(mucLightService, userJid);
|
|
||||||
|
|
||||||
// Block several users
|
|
||||||
multiUserChatLightManager.blockUsers(mucLightService, usersJids);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
*userJid* is a `Jid`
|
|
||||||
|
|
||||||
*usersJids* is a `List<Jid>`
|
|
||||||
|
|
||||||
**Unblock rooms**
|
|
||||||
|
|
||||||
```
|
|
||||||
// Unblock one room
|
|
||||||
multiUserChatLightManager.unblockRoom(mucLightService, roomJid);
|
|
||||||
|
|
||||||
// Unblock several rooms
|
|
||||||
multiUserChatLightManager.unblockRooms(mucLightService, roomsJids);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
*roomJid* is a `Jid`
|
|
||||||
|
|
||||||
*roomsJids* is a `List<Jid>`
|
|
||||||
|
|
||||||
**Unblock users**
|
|
||||||
|
|
||||||
```
|
|
||||||
// Unblock one user
|
|
||||||
multiUserChatLightManager.unblockUser(mucLightService, userJid);
|
|
||||||
|
|
||||||
// Unblock several users
|
|
||||||
multiUserChatLightManager.unblockUsers(mucLightService, usersJids);
|
|
||||||
```
|
|
||||||
*mucLightService* is a `DomainBareJid`
|
|
||||||
|
|
||||||
*userJid* is a `Jid`
|
|
||||||
|
|
||||||
*usersJids* is a `List<Jid>`
|
|
|
@ -1,297 +0,0 @@
|
||||||
Encrypting messages with OMEMO
|
|
||||||
==============================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
About OMEMO
|
|
||||||
-----------
|
|
||||||
|
|
||||||
OMEMO ([XEP-0384](https://xmpp.org/extensions/xep-0384.html)) is an adaption
|
|
||||||
of the Signal protocol for XMPP. It provides an important set of
|
|
||||||
cryptographic properties including but not restricted to
|
|
||||||
|
|
||||||
* Confidentiality
|
|
||||||
* Integrity
|
|
||||||
* Authenticity
|
|
||||||
* Forward secrecy
|
|
||||||
* Future secrecy (break-in recovery)
|
|
||||||
* Plausible deniability
|
|
||||||
|
|
||||||
Contrary to OTR it is capable of multi-end-to-multi-end encryption and
|
|
||||||
message synchronization across multiple devices. It also allows the sender
|
|
||||||
to send a message while the recipient is offline.
|
|
||||||
|
|
||||||
It does NOT provide a server side message archive, so that a new device could
|
|
||||||
fetch old chat history.
|
|
||||||
|
|
||||||
Most implementations of OMEMO use the signal-protocol libraries provided by
|
|
||||||
OpenWhisperSystems. Unlike Smack, those libraries are licensed under the GPLv3,
|
|
||||||
which prevents a Apache licensed OMEMO implementation using those libraries (see
|
|
||||||
[licensing situation](https://github.com/igniterealtime/Smack/wiki/OMEMO-libsignal-Licensing-Situation)).
|
|
||||||
The module smack-omemo therefore contains no code related to signal-protocol.
|
|
||||||
However, almost all functionality is encapsulated in that module. If you want
|
|
||||||
to use OMEMO in a GPLv3 licensed client, you can use the smack-omemo-signal
|
|
||||||
Smack module, which binds the signal-protocol library to smack-omemo.
|
|
||||||
It is also possible, to port smack-omemo to other libraries implementing the
|
|
||||||
double ratchet algorithm.
|
|
||||||
|
|
||||||
Understanding the Double Ratchet Algorithm
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
In the context of OMEMO encryption, a *recipient* is a not a user, but a users *device* (a user might have
|
|
||||||
multiple devices of course).
|
|
||||||
Unlike in PGP, each device capable of OMEMO has its own identity key and publishes its own key bundle.
|
|
||||||
It is not advised to migrate OMEMO identities from one device to another, as it might damage the ratchet
|
|
||||||
if not done properly (more on that later). Sharing one identity key between multiple devices is not the purpose of
|
|
||||||
OMEMO. If a contact has three OMEMO capable devices, you will see three different OMEMO identities and their
|
|
||||||
fingerprints.
|
|
||||||
|
|
||||||
OMEMO utilizes multiple layers of encryption when encrypting a message.
|
|
||||||
The body of the message is encrypted with a symmetric message key (AES-128-GCM) producing a *payload*.
|
|
||||||
The message key is encrypted for each recipient using the double ratchet algorithm.
|
|
||||||
For that purpose, the sending device creates a session with the recipient device (if there was no session already).
|
|
||||||
Upon receiving a message, the recipient selects the encrypted key addressed to them and decrypts it with their
|
|
||||||
counterpart of the OMEMO session. The decrypted key gets then used to decrypt the message.
|
|
||||||
|
|
||||||
One important consequence of forward secrecy is, that whenever an OMEMO message gets decrypted,
|
|
||||||
the state of the ratchet changes and the key used to decrypt the message gets deleted.
|
|
||||||
There is no way to recover this key a second time. The result is, that every message can be decrypted
|
|
||||||
exactly once.
|
|
||||||
|
|
||||||
In order to provide the best user experience, it is therefore advised to implement a client side message archive,
|
|
||||||
since solutions like MAM cannot be used to fetch old, already once decrypted OMEMO messages.
|
|
||||||
|
|
||||||
Server-side Requirements
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
In order to use OMEMO encryption, your server and the servers of your chat
|
|
||||||
partners must support PEP ([XEP-0163](http://xmpp.org/extensions/xep-0163.html))
|
|
||||||
to store and exchange key bundles.
|
|
||||||
Optionally your server should support Message Carbons ([XEP-0280](http://xmpp.org/extensions/xep-0280.html))
|
|
||||||
and Message Archive Management ([XEP-0313](http://xmpp.org/extensions/xep-0313.html))
|
|
||||||
to achieve message synchronization across all (on- and offline) devices.
|
|
||||||
|
|
||||||
Client-side Requirements
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
If you are want to run smack-omemo related code on the Windows platform, you might have to install the
|
|
||||||
[Java Cryptography Extension](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html).
|
|
||||||
This is needed to generate cryptographically strong keys.
|
|
||||||
|
|
||||||
Storing Keys
|
|
||||||
------------
|
|
||||||
|
|
||||||
smack-omemo needs to create, store and delete some information like keys and session states during operation.
|
|
||||||
For that purpose the `OmemoStore` class is used. There are multiple implementations with different properties.
|
|
||||||
|
|
||||||
* The `(Signal)FileBasedOmemoStore` stores all information in individual files organized in a directory tree.
|
|
||||||
While this is the most basic and easy to use implementation, it is not the best solution in terms of performance.
|
|
||||||
|
|
||||||
* The `(Signal)CachingOmemoStore` is a multi-purpose store implementation. It can be used to wrap another
|
|
||||||
`(Signal)OmemoStore` implementation to provide a caching layer (for example in order to reduce access to a database backend or
|
|
||||||
a the file system of the `FileBasedOmemoStore`. It is therefore advised to wrap persistent `(Signal)OmemoStore`
|
|
||||||
implementations with a `(Signal)CachingOmemoStore`.
|
|
||||||
On the other hand it can also be used standalone as an ephemeral `OmemoStore`, which "forgets" all stored information
|
|
||||||
once the program terminates. This comes in handy for testing purposes.
|
|
||||||
|
|
||||||
If you are unhappy with the `(Signal)FileBasedOmemoStore`, you can implement your own store (for example with a
|
|
||||||
SQL database) by extending the `(Signal)OmemoStore` class.
|
|
||||||
|
|
||||||
It most certainly makes sense to store the data of the used `OmemoStore` in a secure way (for example an
|
|
||||||
encrypted database).
|
|
||||||
|
|
||||||
Handling Trust Decisions
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
In order for a cryptographic system to make sense, decisions must be made whether to *trust* an identity or not.
|
|
||||||
For technical reasons those decisions cannot be stored within the `OmemoStore`. Instead a client must implement
|
|
||||||
the `OmemoTrustCallback`. This interface provides methods to mark `OmemoFingerprints` as trusted or untrusted and to
|
|
||||||
query trust decisions.
|
|
||||||
|
|
||||||
In order to provide security, a client should communicate to the user, that it is important for them to compare
|
|
||||||
fingerprints through an external channel (reading it out on the phone, scanning QR codes...) before starting to chat.
|
|
||||||
|
|
||||||
While not implemented in smack-omemo, it is certainly for the client to implement different trust models like
|
|
||||||
[Blind Trust Before Verification](https://gultsch.de/trust.html).
|
|
||||||
|
|
||||||
Basic Setup
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Before you can start to send and receive messages, some preconditions have to be met. These steps should be executed
|
|
||||||
in the order as presented below. In this example we will use components from the *smack-omemo-signal* module.
|
|
||||||
|
|
||||||
1. Register an OmemoService
|
|
||||||
|
|
||||||
The `OmemoService` class is responsible for handling incoming messages and manages access to the Double Ratchet.
|
|
||||||
|
|
||||||
```
|
|
||||||
SignalOmemoService.setup();
|
|
||||||
```
|
|
||||||
|
|
||||||
The `setup()` method registers the service as a singleton. You can later access the instance
|
|
||||||
by calling `SignalOmemoService.getInstace()`. The service can only be registered once.
|
|
||||||
Subsequent calls will throw an `IllegalStateException`.
|
|
||||||
|
|
||||||
2. Set an OmemoStore
|
|
||||||
|
|
||||||
Now you have to decide, what `OmemoStore` implementation you want to use to store and access
|
|
||||||
keys and session states. In this example we'll use the `SignalFileBasedOmemoStore` wrapped in a
|
|
||||||
`SignalCachingOmemoStore` for better performance.
|
|
||||||
|
|
||||||
```
|
|
||||||
SignalOmemoService service = SignalOmemoService.getInstace();
|
|
||||||
service.setOmemoStoreBackend(new SignalCachingOmemoStore(new SignalFileBasedOmemoStore(new File("/path/to/store"))));
|
|
||||||
```
|
|
||||||
|
|
||||||
Just like the `OmemoService` instance, the `OmemoStore` instance can only be set once.
|
|
||||||
|
|
||||||
3. Get an instance of the OmemoManager for your connection
|
|
||||||
|
|
||||||
For the greater part of OMEMO related actions, you'll use the `OmemoManager`. The `OmemoManager` represents
|
|
||||||
your OMEMO device. While it is possible to have multiple `OmemoManager`s per `XMPPConnection`, you really
|
|
||||||
only need one.
|
|
||||||
|
|
||||||
```
|
|
||||||
OmemoManager manager = OmemoManager.getInstanceFor(connection);
|
|
||||||
```
|
|
||||||
|
|
||||||
If for whatever reason you decide to use multiple `OmemoManager`s at once,
|
|
||||||
it is highly advised to get them like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
OmemoManager first = OmemoManager.getInstanceFor(connection, firstId);
|
|
||||||
OmemoManager second = OmemoManager.getInstanceFor(connection, secondId);
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Set an OmemoTrustCallback
|
|
||||||
|
|
||||||
As stated above, the `OmemoTrustCallback` is used to query trust decisions. Set the callback like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
manager.setTrustCallback(trustCallback);
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use multiple `OmemoManager`s each `OmemoManager` MUST have its own callback.
|
|
||||||
|
|
||||||
5. Set listeners for OMEMO messages.
|
|
||||||
|
|
||||||
To get notified of incoming OMEMO encrypted messages, you need to register corresponding listeners.
|
|
||||||
There are two types of listeners.
|
|
||||||
|
|
||||||
* `OmemoMessageListener` is used to listen for incoming encrypted OMEMO single chat messages and
|
|
||||||
KeyTransportMessages.
|
|
||||||
* `OmemoMucMessageListener` is used to listen for encrypted OMEMO messages sent in a MultiUserChat.
|
|
||||||
|
|
||||||
Note that an incoming message might not have a body. That might be the case for
|
|
||||||
[KeyTransportMessages](https://xmpp.org/extensions/xep-0384.html#usecases-keysend)
|
|
||||||
or messages sent to update the ratchet. You can check, whether a received message is such a message by calling
|
|
||||||
`OmemoMessage.Received.isKeyTransportMessage()`, which will return true if the message has no body.
|
|
||||||
|
|
||||||
The received message will include the senders device and fingerprint, which you can use in
|
|
||||||
`OmemoManager.isTrustedOmemoIdentity(device, fingerprint)` to determine, if the message was sent by a trusted device.
|
|
||||||
|
|
||||||
6. Initialize the manager(s)
|
|
||||||
|
|
||||||
Ideally all above steps should be executed *before* `connection.login()` gets called. That way you won't miss
|
|
||||||
any offline messages. If the connection is not yet logged in, now is the time to do so.
|
|
||||||
|
|
||||||
```
|
|
||||||
connection.login();
|
|
||||||
manager.initialize();
|
|
||||||
```
|
|
||||||
|
|
||||||
Since a lot of keys are generated in this step, this might take a little longer on older devices.
|
|
||||||
You might want to use the asynchronous call `OmemoManager.initializeAsync(initializationFinishedCallback)`
|
|
||||||
instead to prevent the thread from blocking.
|
|
||||||
|
|
||||||
Send Messages
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Encrypting a message for a contact really means to encrypt the message for all trusted devices of the contact, as well
|
|
||||||
as all trusted devices of the user itself (except the sending device). The encryption process will fail if there are
|
|
||||||
devices for which the user has not yet made a trust decision.
|
|
||||||
|
|
||||||
### Make Trust Decisions
|
|
||||||
|
|
||||||
To get a list of all devices of a contact, you can do the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
List<OmemoDevice> devices = manager.getDevicesOf(contactsBareJid);
|
|
||||||
```
|
|
||||||
|
|
||||||
To get the OmemoFingerprint of a device, you can call
|
|
||||||
|
|
||||||
```
|
|
||||||
OmemoFingerprint fingerprint = manager.getFingerprint(device);
|
|
||||||
```
|
|
||||||
|
|
||||||
This fingerprint can now be displayed to the user who can decide whether to trust the device, or not.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Trust
|
|
||||||
manager.trustOmemoIdentity(device, fingerprint);
|
|
||||||
|
|
||||||
// Distrust
|
|
||||||
manager.distrustOmemoIdentity(device, fingerprint);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Encrypt a Message
|
|
||||||
|
|
||||||
Currently only Message bodies can be encrypted.
|
|
||||||
```
|
|
||||||
String secret = "Mallory is a twerp!";
|
|
||||||
OmemoMessage.Sent encrypted = manager.encrypt(contactsBareJid, secret);
|
|
||||||
```
|
|
||||||
|
|
||||||
The encrypted message will contain some information about the message. It might for example happen, that the encryption
|
|
||||||
failed for some recipient devices. For that reason the encrypted message will contain a map of skipped devices and
|
|
||||||
the reasons.
|
|
||||||
|
|
||||||
### Encrypt a Message for a MultiUserChat
|
|
||||||
|
|
||||||
A MultiUserChat must fulfill some criteria in order to be OMEMO capable.
|
|
||||||
The MUC must be non-anonymous. Furthermore all members of the MUC must have subscribed to one another.
|
|
||||||
You can check for the non-anonymity like follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
manager.multiUserChatSupportsOmemo(muc);
|
|
||||||
```
|
|
||||||
|
|
||||||
Encryption is then done analog to single message encryption:
|
|
||||||
|
|
||||||
```
|
|
||||||
OmemoMessage.Sent encrypted = manager.encrypt(multiUserChat, secret);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sending an encrypted Message
|
|
||||||
|
|
||||||
To send the message, it has to be wrapped in a `Message` object. That can conveniently be done like follows.
|
|
||||||
|
|
||||||
```
|
|
||||||
Message message = encrypted.asMessage(contactsJid);
|
|
||||||
connection.sendStanza(message):
|
|
||||||
```
|
|
||||||
|
|
||||||
This will add a [Message Processing Hint](https://xmpp.org/extensions/xep-0334.html) for MAM,
|
|
||||||
an [Explicit Message Encryption](https://xmpp.org/extensions/xep-0380.html) hint for OMEMO,
|
|
||||||
as well as an optional cleartext hint about OMEMO to the message.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
smack-omemo has some configuration options that can be changed on runtime via the `OmemoConfiguration` class:
|
|
||||||
|
|
||||||
* setIgnoreStaleDevices when set to true, smack-omemo will stop encrypting messages for **own** devices that have not send a message for some period of time (configurable in setIgnoreStaleDevicesAfterHours)
|
|
||||||
* setDeleteStaleDevices when set to true, smack-omemo will remove own devices from the device list, if no messages were received from them for a period of time (configurable in setDeleteStaleDevicesAfterHours)
|
|
||||||
* setRenewOldSignedPreKeys when set to true, smack-omemo will periodically generate and publish new signed prekeys. Via setRenewOldSignedPreKeysAfterHours you can configure, after what period of time new keys are generated and setMaxNumberOfStoredSignedPreKeys allows configuration of how many signed PreKeys are kept in storage for decryption of delayed messages.
|
|
||||||
* setAddOmemoBodyHint when set to true, a plaintext body with a hint about OMEMO encryption will be added to the message. This hint will be displayed by clients that do not support OMEMO. Note that this might not be desirable when communicating with clients that do not support EME.
|
|
||||||
* setRepairBrokenSessionsWithPreKeyMessages when set to true, whenever a message arrives, which cannot be decrypted, smack-omemo will respond with a preKeyMessage which discards the old session and builds a fresh one.
|
|
||||||
* setCompleteSessionWithEmptyMessage when set to true, whenever a preKeyMessage arrives, smack-omemo will respond with an empty message to complete the session.
|
|
||||||
|
|
||||||
Integration Tests
|
|
||||||
-----------------
|
|
||||||
smack-omemo comes with a set of integration tests. Lets say you want to run the integration test suite for smack-omemo-signal.
|
|
||||||
You can do so by using the following gradle task:
|
|
||||||
|
|
||||||
```
|
|
||||||
gradle omemoSignalIntTest
|
|
||||||
```
|
|
|
@ -1,43 +0,0 @@
|
||||||
Migrating smack-omemo from 4.2.1 to 4.x.x
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
The implementation of smack-omemo and smack-omemo-signal was originally started as an
|
|
||||||
academic project under pressure of time.
|
|
||||||
For that reason, the API was not perfect when OMEMO support was first introduced in
|
|
||||||
Smack in version 4.2.1.
|
|
||||||
Many issues of smack-omemo have been resolved over the course of the last year in
|
|
||||||
a major effort, which is why smack-omemo and smack-omemo-signalwere excluded from
|
|
||||||
the 4.2.2 release.
|
|
||||||
|
|
||||||
During this time major parts of the implementation were redone and the API changed
|
|
||||||
as a consequence of that. This guide will go through all notable changes in order
|
|
||||||
to make the process of upgrading as easy and straight forward as possible.
|
|
||||||
|
|
||||||
## Trust
|
|
||||||
One major change is, that the OmemoStore implementations no longer store trust decisions.
|
|
||||||
Methods related to trust have been removed from OmemoStore implementations.
|
|
||||||
Instead the client is now responsible to store those.
|
|
||||||
Upon startup, the client now must pass an `OmemoTrustCallback` to the `OmemoManager`
|
|
||||||
which is used to access and change trust decisions.
|
|
||||||
|
|
||||||
It is recommended for the client to store trust decisions as tuples of (omemo device,
|
|
||||||
fingerprint of identityKey, trust state).
|
|
||||||
When querying a trust decision (aka. "Is this fingerprint trusted for that device?),
|
|
||||||
the local fingerprint should be compared to the provided fingerprint.
|
|
||||||
|
|
||||||
The method signatures for setting and querying trust from inside the OmemoManager are
|
|
||||||
still the same. Internally they access the `OmemoTrustCallback` set by the client.
|
|
||||||
|
|
||||||
## Encryption
|
|
||||||
Message encryption in smack-omemo 4.2.1 was ugly. Encryption for multiple devices
|
|
||||||
could fail because session negotiation could go wrong, which resulted in an
|
|
||||||
exception, which contained all devices with working sessions.
|
|
||||||
That exception could then be used in
|
|
||||||
`OmemoManager.encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message)`,
|
|
||||||
to encrypt the message for all devices with a session.
|
|
||||||
|
|
||||||
The new API is
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
OpenPGP for XMPP: Instant Messaging
|
|
||||||
===================================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
See the javadoc of `OpenPgpManager` and `OXInstantMessagingManager` for details.
|
|
|
@ -1,6 +0,0 @@
|
||||||
OpenPGP for XMPP
|
|
||||||
================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
See the javadoc of `OpenPgpManager` for details.
|
|
|
@ -1,141 +0,0 @@
|
||||||
Privacy Lists
|
|
||||||
============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
[XEP-0016: Privacy Lists](http://xmpp.org/extensions/xep-0016.html)
|
|
||||||
|
|
||||||
What is it?
|
|
||||||
-----------
|
|
||||||
|
|
||||||
`Privacy` is a method for users to block communications from particular other
|
|
||||||
users. In XMPP this is done by managing one's privacy lists.
|
|
||||||
|
|
||||||
Server-side privacy lists enable successful completion of the following use
|
|
||||||
cases:
|
|
||||||
|
|
||||||
* Retrieving one's privacy lists.
|
|
||||||
* Adding, removing, and editing one's privacy lists.
|
|
||||||
* Setting, changing, or declining active lists.
|
|
||||||
* Setting, changing, or declining the default list (i.e., the list that is active by default).
|
|
||||||
* Allowing or blocking messages based on JID, group, or subscription type (or globally).
|
|
||||||
* Allowing or blocking inbound presence notifications based on JID, group, or subscription type (or globally).
|
|
||||||
* Allowing or blocking outbound presence notifications based on JID, group, or subscription type (or globally).
|
|
||||||
* Allowing or blocking IQ stanzas based on JID, group, or subscription type (or globally).
|
|
||||||
* Allowing or blocking all communications based on JID, group, or subscription type (or globally).
|
|
||||||
|
|
||||||
How can I use it?
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The API implementation releases three main public classes:
|
|
||||||
|
|
||||||
* `PrivacyListManager`: this is the main API class to retrieve and handle server privacy lists.
|
|
||||||
* `PrivacyList`: witch represents one privacy list, with a name, a set of privacy items. For example, the list with visible or invisible.
|
|
||||||
* `PrivacyItem`: block or allow one aspect of privacy. For example, to allow my friend to see my presence.
|
|
||||||
|
|
||||||
1. Right from the start, a client MAY **get his/her privacy list** that is stored in the server:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Retrieve server privacy lists_
|
|
||||||
PrivacyList[] lists = privacyManager.getPrivacyLists();
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the client is able to show every `PrivacyItem` of the server and also for
|
|
||||||
every list if it is active, default or none of them. The client is a listener
|
|
||||||
of privacy changes.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2. In order to **add a new list in the server**, the client MAY implement something like:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Set the name of the list_
|
|
||||||
String listName = "newList";
|
|
||||||
|
|
||||||
// Create the list of PrivacyItem that will allow or deny some privacy aspect_
|
|
||||||
String user = "tybalt@example.com";
|
|
||||||
String groupName = "enemies";
|
|
||||||
ArrayList privacyItems = new ArrayList();
|
|
||||||
|
|
||||||
PrivacyItem item = new PrivacyItem(PrivacyItem.Type.jid, user, true, 1);
|
|
||||||
privacyItems.add(item);
|
|
||||||
|
|
||||||
item = new PrivacyItem(PrivacyItem.Type.subscription, PrivacyItem.SUBSCRIPTION_BOTH, true, 2);
|
|
||||||
privacyItems.add(item);
|
|
||||||
|
|
||||||
item = new PrivacyItem(PrivacyItem.Type.group, groupName, false, 3);
|
|
||||||
item.setFilterMessage(true);
|
|
||||||
privacyItems.add(item);
|
|
||||||
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Create the new list._
|
|
||||||
privacyManager.createPrivacyList(listName, privacyItems);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. To **modify an existent list**, the client code MAY be like:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Set the name of the list_
|
|
||||||
String listName = "existingList";
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Sent the new list to the server._
|
|
||||||
privacyManager.updatePrivacyList(listName, items);
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice `items` was defined at the example 2 and MUST contain all the elements
|
|
||||||
in the list (not the "delta").
|
|
||||||
|
|
||||||
4. In order to **delete an existing list**, the client MAY perform something like:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Set the name of the list_
|
|
||||||
String listName = "existingList";
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Remove the list._
|
|
||||||
privacyManager.deletePrivacyList(listName);
|
|
||||||
```
|
|
||||||
|
|
||||||
5. In order to **decline the use of an active list**, the client MAY perform something like:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Decline the use of the active list._
|
|
||||||
privacyManager.declineActiveList();
|
|
||||||
```
|
|
||||||
|
|
||||||
6. In order to **decline the use of a default list**, the client MAY perform something like:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Decline the use of the default list._
|
|
||||||
privacyManager.declineDefaultList();
|
|
||||||
```
|
|
||||||
|
|
||||||
Listening for Privacy Changes
|
|
||||||
|
|
||||||
In order to handle privacy changes, clients SHOULD listen manager's updates.
|
|
||||||
When a list is changed the manager notifies every added listener. Listeners
|
|
||||||
MUST implement the `PrivacyListListener` interface. Clients may need to react
|
|
||||||
when a privacy list is modified. The `PrivacyListManager` lets you add
|
|
||||||
listerners that will be notified when a list has been changed. Listeners
|
|
||||||
should implement the `PrivacyListListener` interface.
|
|
||||||
|
|
||||||
The most important notification is `updatedPrivacyList` that is performed when
|
|
||||||
a privacy list changes its privacy items.
|
|
||||||
|
|
||||||
The listener becomes notified after performing:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get the privacy manager for the current connection._
|
|
||||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
|
|
||||||
// Add the listener (this) to get notified_
|
|
||||||
privacyManager.addListener(this);
|
|
||||||
```
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,19 +0,0 @@
|
||||||
Private Data
|
|
||||||
============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Manages private data, which is a mechanism to allow users to store arbitrary
|
|
||||||
XML data on an XMPP server. Each private data chunk is defined by a element
|
|
||||||
name and XML namespace. Example private data:
|
|
||||||
|
|
||||||
```
|
|
||||||
<color xmlns="http://example.com/xmpp/color">
|
|
||||||
<favorite>blue</blue>
|
|
||||||
<leastFavorite>puce</leastFavorite>
|
|
||||||
</color>
|
|
||||||
```
|
|
||||||
|
|
||||||
**XEP related:** [XEP-49](http://www.xmpp.org/extensions/xep-0049.html)
|
|
||||||
|
|
||||||
_More coming soon._
|
|
|
@ -1,85 +0,0 @@
|
||||||
Stanza Properties
|
|
||||||
=================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Smack provides an easy mechanism for attaching arbitrary properties to
|
|
||||||
packets. Each property has a String name, and a value that is a Java primitive
|
|
||||||
(int, long, float, double, boolean) or any Serializable object (a Java object
|
|
||||||
is Serializable when it implements the Serializable interface).
|
|
||||||
|
|
||||||
Using the API
|
|
||||||
-------------
|
|
||||||
|
|
||||||
All major objects have property support, such as Message objects. The
|
|
||||||
following code demonstrates how to set properties:
|
|
||||||
|
|
||||||
```
|
|
||||||
Message message = chat.createMessage();
|
|
||||||
JivePropertiesExtension jpe = new JivePropertiesExtension();
|
|
||||||
// Add a Color object as a property._
|
|
||||||
jpe.setProperty("favoriteColor", new Color(0, 0, 255));
|
|
||||||
// Add an int as a property._
|
|
||||||
jpe.setProperty("favoriteNumber", 4);
|
|
||||||
// Add the JivePropertiesExtension to the message packet_
|
|
||||||
message.addStanzaExtension(jpe);
|
|
||||||
chat.sendMessage(message);
|
|
||||||
```
|
|
||||||
|
|
||||||
Getting those same properties would use the following code:
|
|
||||||
|
|
||||||
```
|
|
||||||
Message message = chat.nextMessage();
|
|
||||||
// Get the JivePropertiesExtension_
|
|
||||||
JivePropertiesExtension jpe = message.getExtension(JivePropertiesExtension.NAMESPACE);
|
|
||||||
// Get a Color object property._
|
|
||||||
Color favoriteColor = (Color)jpe.getProperty("favoriteColor");
|
|
||||||
// Get an int property. Note that properties are always returned as
|
|
||||||
// Objects, so we must cast the value to an Integer, then convert
|
|
||||||
// it to an int._
|
|
||||||
int favoriteNumber = ((Integer)jpe.getProperty("favoriteNumber")).intValue();
|
|
||||||
```
|
|
||||||
|
|
||||||
For convenience `JivePropertiesManager` contains two helper methods namely
|
|
||||||
`addProperty(Stanza packet, String name, Object value)` and
|
|
||||||
`getProperty(Stanza packet, String name)`.
|
|
||||||
|
|
||||||
Objects as Properties
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Using objects as property values is a very powerful and easy way to exchange
|
|
||||||
data. However, you should keep the following in mind:
|
|
||||||
|
|
||||||
* When you send a Java object as a property, only clients running Java will be able to interpret the data. So, consider using a series of primitive values to transfer data instead.
|
|
||||||
* Objects sent as property values must implement Serialiable. Additionally, both the sender and receiver must have identical versions of the class, or a serialization exception will occur when de-serializing the object.
|
|
||||||
* Serialized objects can potentially be quite large, which will use more bandwidth and server resources.
|
|
||||||
|
|
||||||
XML Format
|
|
||||||
----------
|
|
||||||
|
|
||||||
The current XML format used to send property data is not a standard, so will
|
|
||||||
likely not be recognized by clients not using Smack. The XML looks like the
|
|
||||||
following (comments added for clarity):
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- All properties are in a x block. -->
|
|
||||||
<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties">
|
|
||||||
<!-- First, a property named "prop1" that's an integer. -->
|
|
||||||
<property>
|
|
||||||
<name>prop1</name>
|
|
||||||
<value type="integer">123</value>
|
|
||||||
<property>
|
|
||||||
<!-- Next, a Java object that's been serialized and then converted
|
|
||||||
from binary data to base-64 encoded text. -->
|
|
||||||
<property>
|
|
||||||
<name>blah2</name>
|
|
||||||
<value type="java-object">adf612fna9nab</value>
|
|
||||||
<property>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
|
|
||||||
The currently supported types are: `integer`, `long`, `float`, `double`,
|
|
||||||
`boolean`, `string`, and `java-object`.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,404 +0,0 @@
|
||||||
Pubsub
|
|
||||||
======
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
This section details the usage of an API designed for accessing an XMPP based
|
|
||||||
implementation of a [publish and
|
|
||||||
subscribe](http://en.wikipedia.org/wiki/Publish/subscribe) based messaging
|
|
||||||
system. It has functionality for creation, configuration of, subscription and
|
|
||||||
publishing to pubsub nodes.
|
|
||||||
|
|
||||||
* Node creation and configuration
|
|
||||||
* Publishing to a node
|
|
||||||
* Receiving pubsub messages
|
|
||||||
* Retrieving persisted pubsub messages
|
|
||||||
* Discover pubsub information
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0060](http://xmpp.org/extensions/xep-0060.html)
|
|
||||||
|
|
||||||
Node creation and configuration
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
Allowed users may create and configure pubsub nodes. There are two types of
|
|
||||||
nodes that can be created, leaf nodes and collection nodes.
|
|
||||||
|
|
||||||
* Leaf Nodes - contains only messages
|
|
||||||
* Collection Nodes - contains only nodes (both Leaf and Collection are allowed), but no messages
|
|
||||||
The current version of this API only supports Leaf Nodes. There are many
|
|
||||||
configuration options available for nodes, but the two main options are
|
|
||||||
whether the node is **persistent** or not and whether it will deliver payload
|
|
||||||
or not.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
In order to create a node you will need to first create an instance of
|
|
||||||
_**PubSubManager**_. There are several options for node creation which range
|
|
||||||
from creating an instant node, default configuration, or a fully configured
|
|
||||||
node.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
Create an instant node:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = new PubSubManager(con);
|
|
||||||
|
|
||||||
// Create the node
|
|
||||||
LeafNode leaf = mgr.createNode();
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a node with default configuration and then configure it:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Create the node
|
|
||||||
LeafNode leaf = mgr.createNode("testNode");
|
|
||||||
ConfigureForm form = new ConfigureForm(FormType.submit);
|
|
||||||
form.setAccessModel(AccessModel.open);
|
|
||||||
form.setDeliverPayloads(false);
|
|
||||||
form.setNotifyRetract(true);
|
|
||||||
form.setPersistentItems(true);
|
|
||||||
form.setPublishModel(PublishModel.open);
|
|
||||||
|
|
||||||
leaf.sendConfigurationForm(form);
|
|
||||||
```
|
|
||||||
|
|
||||||
Create and configure a node:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Create the node
|
|
||||||
ConfigureForm form = new ConfigureForm(FormType.submit);
|
|
||||||
form.setAccessModel(AccessModel.open);
|
|
||||||
form.setDeliverPayloads(false);
|
|
||||||
form.setNotifyRetract(true);
|
|
||||||
form.setPersistentItems(true);
|
|
||||||
form.setPublishModel(PublishModel.open);
|
|
||||||
LeafNode leaf = mgr.createNode("testNode", form);
|
|
||||||
```
|
|
||||||
|
|
||||||
Publishing to a node
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
This section deals with the **publish** portion of pubsub. Usage of a node
|
|
||||||
typically involves either sending or receiving data, referred to as items.
|
|
||||||
Depending on the context of the nodes usage, the item being sent to it can
|
|
||||||
have different properties. It can contain application data known as payload,
|
|
||||||
or the publisher may choose to supply meaningful unique id's. Determination of
|
|
||||||
an items acceptable properties is defined by a combination of node
|
|
||||||
configuration and its purpose.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To publish to a node, you will have to either create or retrieve an existing
|
|
||||||
node and then create and send items to that node. The ability for any given
|
|
||||||
person to publish to the node will be dependent on its configuration.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we publish an item to a node that does not take payload:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
// Publish an Item, let service set the id
|
|
||||||
node.send(new Item());
|
|
||||||
|
|
||||||
// Publish an Item with the specified id
|
|
||||||
node.send(new Item("123abc"));
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we publish an item to a node that does take payload:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
// Publish an Item with payload
|
|
||||||
node.send(new PayloadItem("test" + System.currentTimeMillis(),
|
|
||||||
new SimplePayload("book", "pubsub:test:book", "Two Towers")));
|
|
||||||
```
|
|
||||||
|
|
||||||
Receiving pubsub messages
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
This section deals with the **subscribe** portion of pubsub. As mentioned in
|
|
||||||
the last section, usage of a node typically involves either sending or
|
|
||||||
receiving items. Subscribers are interested in being notified when items are
|
|
||||||
published to the pubsub node. These items may or may not have application
|
|
||||||
specific data (payload), as that is dependent on the context in which the node
|
|
||||||
is being used.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To get messages asynchronously when items are published to a node, you will
|
|
||||||
have to
|
|
||||||
|
|
||||||
* Get a node.
|
|
||||||
* Create and register a listener.
|
|
||||||
* Subscribe to the node.
|
|
||||||
|
|
||||||
Please note that you should register the listener before subscribing so that
|
|
||||||
all messages sent after subscribing are received. If done in the reverse
|
|
||||||
order, messages that are sent after subscribing but before registering a
|
|
||||||
listener may not be processed as expected.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to create a listener and register it and then
|
|
||||||
subscribe for messages.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
node.addItemEventListener(new ItemEventCoordinator<Item;>());
|
|
||||||
node.subscribe(myJid);
|
|
||||||
```
|
|
||||||
|
|
||||||
Where the listener is defined like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
class ItemEventCoordinator implements ItemEventListener {
|
|
||||||
@Override
|
|
||||||
public void handlePublishedItems(ItemPublishEvent items) {
|
|
||||||
System.out.println("Item count: " + System.out.println(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition to receiving published items, there are notifications for several
|
|
||||||
other events that occur on a node as well.
|
|
||||||
|
|
||||||
* Deleting items or purging all items from a node
|
|
||||||
* Changing the node configuration
|
|
||||||
|
|
||||||
In this example we can see how to create a listener, register it and then
|
|
||||||
subscribe for item deletion messages.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
node.addItemDeleteListener(new ItemDeleteCoordinator<Item;>());
|
|
||||||
node.subscribe(myJid);
|
|
||||||
node.deleteItem("id_one");
|
|
||||||
```
|
|
||||||
|
|
||||||
Where the handler is defined like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
class ItemDeleteCoordinator implements ItemDeleteListener {
|
|
||||||
@Override
|
|
||||||
public void handleDeletedItems(ItemDeleteEvent items) {
|
|
||||||
System.out.println("Item count: " + items.getItemIds().size());
|
|
||||||
System.out.println(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePurge() {
|
|
||||||
System.out.println("All items have been deleted from node");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to create a listener, register it and then
|
|
||||||
subscribe for node configuration messages.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
Node node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
node.addConfigurationListener(new NodeConfigCoordinator());
|
|
||||||
node.subscribe(myJid);
|
|
||||||
|
|
||||||
ConfigureForm form = new ConfigureForm(FormType.submit);
|
|
||||||
form.setAccessModel(AccessModel.open);
|
|
||||||
form.setDeliverPayloads(false);
|
|
||||||
form.setNotifyRetract(true);
|
|
||||||
form.setPersistentItems(true);
|
|
||||||
form.setPublishModel(PublishModel.open);
|
|
||||||
|
|
||||||
node.sendConfigurationForm(form);
|
|
||||||
```
|
|
||||||
|
|
||||||
Where the handler is defined like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
class NodeConfigCoordinator implements NodeConfigListener {
|
|
||||||
@Override
|
|
||||||
public void handleNodeConfiguration(ConfigurationEvent config) {
|
|
||||||
System.out.println("New configuration");
|
|
||||||
System.out.println(config.getConfiguration());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Retrieving persisted pubsub messages
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
When persistent nodes are used, the subscription and registration methods
|
|
||||||
described in the last section will not enable the retrieval of items that
|
|
||||||
already exist in the node. This section deals with the specific methods for
|
|
||||||
retrieving these items. There are several means of retrieving existing items.
|
|
||||||
You can retrieve all items at once, the last N items, or the items specified
|
|
||||||
by a collection of id's. Please note that the service may, according to the
|
|
||||||
pubsub specification, reply with a list of items that contains only the item
|
|
||||||
id's (no payload) to save on bandwidth. This will not occur when the id's are
|
|
||||||
specified since this is the means of guaranteeing retrieval of payload.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To synchronously retrieve existing items from a persistent node, you will have
|
|
||||||
to get an instance of a _**LeafNode**_ and call one of the retrieve methods.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to retrieve the existing items from a node:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
Collection<? extends Item> items = node.getItems();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to retrieve the last N existing items:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
List<? extends Item> items = node.getItems(100);
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to retrieve the specified existing items:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
LeafNode node = mgr.getNode("testNode");
|
|
||||||
Collection<String;> ids = new ArrayList<String;>(3);
|
|
||||||
ids.add("1");
|
|
||||||
ids.add("3");
|
|
||||||
ids.add("4");
|
|
||||||
|
|
||||||
List<? extends Item> items = node.getItems(ids);
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover pubsub information
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
A user may want to query a server or node for a variety of pubsub related
|
|
||||||
information.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To retrieve information, a user will simply use either the _**PubSubManager**_
|
|
||||||
or _**Node**_ classes depending on what type of information is required.
|
|
||||||
|
|
||||||
**Examples**
|
|
||||||
|
|
||||||
In this example we can see how to get pubsub capabilities:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the pubsub features that are supported
|
|
||||||
DiscoverInfo supportedFeatures = mgr.getSupportedFeatures();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to get pubsub subscriptions for all nodes:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get all the subscriptions in the pubsub service
|
|
||||||
List<Subscription;> subscriptions = mgr.getSubscriptions();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to get all affiliations for the users bare JID
|
|
||||||
on the pubsub service:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
|
|
||||||
// Get the affiliations for the users bare JID
|
|
||||||
List<Affiliation;> affiliations = mgr.getAffiliations();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to get information about the node:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
Node node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
// Get the node information
|
|
||||||
DiscoverInfo nodeInfo = node.discoverInfo();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to discover the node items:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
Node node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
// Discover the node items
|
|
||||||
DiscoverItems nodeItems = node.discoverItems();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example we can see how to get node subscriptions:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a pubsub manager using an existing XMPPConnection
|
|
||||||
PubSubManager mgr = PubSubManager.getInstanceFor(con);
|
|
||||||
Node node = mgr.getNode("testNode");
|
|
||||||
|
|
||||||
// Discover the node subscriptions
|
|
||||||
List<Subscription;> subscriptions = node.getSubscriptions();
|
|
||||||
```
|
|
|
@ -1,84 +0,0 @@
|
||||||
Push Notifications
|
|
||||||
==================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Allows to manage how XMPP servers deliver information for use in push notifications to mobile and other devices.
|
|
||||||
|
|
||||||
* Check push notifications support
|
|
||||||
* Enable push notifications
|
|
||||||
* Disable push notifications
|
|
||||||
* Remote disabling of push notifications
|
|
||||||
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0357](http://xmpp.org/extensions/xep-0357.html)
|
|
||||||
|
|
||||||
|
|
||||||
Get push notifications manager
|
|
||||||
------------------------------
|
|
||||||
```
|
|
||||||
PushNotificationsManager pushNotificationsManager = PushNotificationsManager.getInstanceFor(connection);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Check push notifications support
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
boolean isSupported = pushNotificationsManager.isSupportedByServer();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Enable push notifications
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
pushNotificationsManager.enable(pushJid, node);
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```
|
|
||||||
pushNotificationsManager.enable(pushJid, node, publishOptions);
|
|
||||||
```
|
|
||||||
*pushJid* is a `Jid`
|
|
||||||
|
|
||||||
*node* is a `String`
|
|
||||||
|
|
||||||
*publishOptions* is a `HashMap<String, String>` (which means [option name, value])
|
|
||||||
|
|
||||||
|
|
||||||
Disable push notifications
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
pushNotificationsManager.disable(pushJid, node);
|
|
||||||
```
|
|
||||||
*pushJid* is a `Jid`
|
|
||||||
|
|
||||||
*node* is a `String`
|
|
||||||
|
|
||||||
**Disable all**
|
|
||||||
|
|
||||||
```
|
|
||||||
pushNotificationsManager.disableAll(pushJid);
|
|
||||||
```
|
|
||||||
*pushJid* is a `Jid`
|
|
||||||
|
|
||||||
|
|
||||||
Remote disabling of push notifications
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
```
|
|
||||||
// check if the message is because remote disabling of push notifications
|
|
||||||
if (message.hasExtension(PushNotificationsElements.RemoteDisablingExtension.ELEMENT, PushNotificationsElements.RemoteDisablingExtension.NAMESPACE)) {
|
|
||||||
|
|
||||||
// Get the remote disabling extension
|
|
||||||
PushNotificationsElements.RemoteDisablingExtension remoteDisablingExtension = PushNotificationsElements.RemoteDisablingExtension.from(message);
|
|
||||||
|
|
||||||
// Get the user Jid
|
|
||||||
Jid userJid = remoteDisablingExtension.getUserJid();
|
|
||||||
|
|
||||||
// Get the node
|
|
||||||
String node = remoteDisablingExtension.getNode();
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,19 +0,0 @@
|
||||||
References
|
|
||||||
==========
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
References are a way to refer to other entities like users, other messages or external data from within a message.
|
|
||||||
|
|
||||||
Typical use-cases are mentioning other users by name, but referencing to their BareJid, or linking to a sent file.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Mention a user and link to their bare jid.
|
|
||||||
```
|
|
||||||
Message message = new Message("Alice is a realy nice person.");
|
|
||||||
BareJid alice = JidCreate.bareFrom("alice@capulet.lit");
|
|
||||||
ReferenceManager.addMention(message, 0, 5, alice);
|
|
||||||
```
|
|
||||||
|
|
||||||
TODO: Add more use cases (for example for MIX, SIMS...)
|
|
|
@ -1,162 +0,0 @@
|
||||||
Roster Item Exchange
|
|
||||||
====================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
This extension is used to send rosters, roster groups and roster entries from
|
|
||||||
one XMPP Entity to another. It also provides an easy way to hook up custom
|
|
||||||
logic when entries are received from other XMPP clients.
|
|
||||||
|
|
||||||
Follow these links to learn how to send and receive roster items:
|
|
||||||
|
|
||||||
* Send a complete roster
|
|
||||||
* Send a roster's group
|
|
||||||
* Send a roster's entry
|
|
||||||
* Receive roster entries
|
|
||||||
|
|
||||||
**XEP related:** [XEP-93](http://www.xmpp.org/extensions/xep-0093.html)
|
|
||||||
|
|
||||||
Send a entire roster
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Sometimes it is useful to send a whole roster to another user. Smack provides
|
|
||||||
a very easy way to send a complete roster to another XMPP client.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Create an instance of _**RosterExchangeManager**_ and use the **#send(Roster,
|
|
||||||
String)** message to send a roster to a given user. The first parameter is the
|
|
||||||
roster to send and the second parameter is the id of the user that will
|
|
||||||
receive the roster entries.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how user1 sends his roster to user2.
|
|
||||||
|
|
||||||
```
|
|
||||||
XMPPConnection conn1 = …
|
|
||||||
|
|
||||||
// Create a new roster exchange manager on conn1
|
|
||||||
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
|
|
||||||
// Send user1's roster to user2
|
|
||||||
rosterExchangeManager.send(Roster.getInstanceFor(conn1), user2);
|
|
||||||
```
|
|
||||||
|
|
||||||
Send a roster group
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
It is also possible to send a roster group to another XMPP client. A roster
|
|
||||||
group groups a set of roster entries under a name.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Create an instance of _**RosterExchangeManager**_ and use the
|
|
||||||
**#send(RosterGroup, String)** message to send a roster group to a given user.
|
|
||||||
The first parameter is the roster group to send and the second parameter is
|
|
||||||
the id of the user that will receive the roster entries.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how user1 sends his roster groups to user2.
|
|
||||||
|
|
||||||
```
|
|
||||||
XMPPConnection conn1 = …
|
|
||||||
|
|
||||||
// Create a new roster exchange manager on conn1
|
|
||||||
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
|
|
||||||
// Send user1's RosterGroups to user2
|
|
||||||
for (Iterator it = Roster.getInstanceFor(conn1).getGroups(); it.hasNext(); )
|
|
||||||
rosterExchangeManager.send((RosterGroup)it.next(), user2);
|
|
||||||
```
|
|
||||||
|
|
||||||
Send a roster entry
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Sometimes you may need to send a single roster entry to another XMPP client.
|
|
||||||
Smack also lets you send items at this granularity level.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Create an instance of _**RosterExchangeManager**_ and use the
|
|
||||||
**#send(RosterEntry, String)** message to send a roster entry to a given user.
|
|
||||||
The first parameter is the roster entry to send and the second parameter is
|
|
||||||
the id of the user that will receive the roster entries.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how user1 sends a roster entry to user2.
|
|
||||||
|
|
||||||
```
|
|
||||||
XMPPConnection conn1 = …
|
|
||||||
|
|
||||||
// Create a new roster exchange manager on conn1
|
|
||||||
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
|
|
||||||
// Send a roster entry (any) to user2
|
|
||||||
rosterExchangeManager1.send((RosterEntry)Roster.getInstanceFor(conn1).getEntries().next(), user2);
|
|
||||||
```
|
|
||||||
|
|
||||||
Receive roster entries
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Since roster items are sent between XMPP clients, it is necessary to listen to
|
|
||||||
possible roster entries receptions. Smack provides a mechanism that you can
|
|
||||||
use to execute custom logic when roster entries are received.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
1. Create a class that implements the _**RosterExchangeListener**_ interface.
|
|
||||||
2. Implement the method **entriesReceived(String, Iterator)** that will be called when new entries are received with custom logic.
|
|
||||||
3. Add the listener to the _RosterExchangeManager_ that works on the desired _XMPPConnection_.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how user1 sends a roster entry to user2 and user2
|
|
||||||
adds the received entries to his roster.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Connect to the server and log in the users
|
|
||||||
XMPPConnection conn1 = …
|
|
||||||
XMPPConnection conn2 = …
|
|
||||||
|
|
||||||
final Roster user2_roster = Roster.getInstanceFor(conn2);
|
|
||||||
|
|
||||||
// Create a RosterExchangeManager that will help user2 to listen and accept
|
|
||||||
the entries received
|
|
||||||
RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(conn2);
|
|
||||||
// Create a RosterExchangeListener that will iterate over the received roster entries
|
|
||||||
RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() {
|
|
||||||
public void entriesReceived(String from, Iterator remoteRosterEntries) {
|
|
||||||
while (remoteRosterEntries.hasNext()) {
|
|
||||||
try {
|
|
||||||
// Get the received entry
|
|
||||||
RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) remoteRosterEntries.next();
|
|
||||||
// Display the remote entry on the console
|
|
||||||
System.out.println(remoteRosterEntry);
|
|
||||||
// Add the entry to the user2's roster
|
|
||||||
user2_roster.createEntry(
|
|
||||||
remoteRosterEntry.getUser(),
|
|
||||||
remoteRosterEntry.getName(),
|
|
||||||
remoteRosterEntry.getGroupArrayNames());
|
|
||||||
}
|
|
||||||
catch (XMPPException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Add the RosterExchangeListener to the RosterExchangeManager that user2 is using
|
|
||||||
rosterExchangeManager2.addRosterListener(rosterExchangeListener);
|
|
||||||
|
|
||||||
// Create a RosterExchangeManager that will help user1 to send his roster
|
|
||||||
RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(conn1);
|
|
||||||
// Send user1's roster to user2
|
|
||||||
rosterExchangeManager1.send(Roster.getInstanceFor(conn1), user2);
|
|
||||||
```
|
|
|
@ -1,33 +0,0 @@
|
||||||
Spoiler Messages
|
|
||||||
================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Spoiler Messages can be used to indicate that the body of a message is a spoiler and should be displayed as such.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To get an instance of the SpoilerManager, call
|
|
||||||
```
|
|
||||||
SpoilerManager manager = SpoilerManager.getInstanceFor(connection);
|
|
||||||
```
|
|
||||||
This will automatically add Spoilers to the list of supported features of your client.
|
|
||||||
|
|
||||||
The manager can then be used to add SpoilerElements to messages like follows:
|
|
||||||
```
|
|
||||||
Message message = new Message();
|
|
||||||
|
|
||||||
// spoiler without hint
|
|
||||||
SpoilerElement.addSpoiler(message);
|
|
||||||
|
|
||||||
// spoiler with hint about content
|
|
||||||
SpoilerElement.addSpoiler(message, "End of Love Story");
|
|
||||||
|
|
||||||
// spoiler with localized hint
|
|
||||||
SpoilerElement.addSpoiler(message, "de", "Der Kuchen ist eine Lüge");
|
|
||||||
```
|
|
||||||
|
|
||||||
To get Spoilers from a message call
|
|
||||||
```
|
|
||||||
Map<String, String> spoilers = SpoilerElement.getSpoilers(message);
|
|
||||||
```
|
|
|
@ -1,39 +0,0 @@
|
||||||
Stream Management
|
|
||||||
=================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
XMPPTCPConnection comes with support for Stream Management (SM).
|
|
||||||
|
|
||||||
**XEP related:** [XEP-0198](http://xmpp.org/extensions/xep-0198.html)
|
|
||||||
|
|
||||||
Known interoperability issues
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
- SM resumption failes on prosody when compression in sync flush mode is used with prosody. See [Prosody issue #433](https://code.google.com/p/lxmppd/issues/detail?id=433).
|
|
||||||
|
|
||||||
Enabling stream management
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Getting notifications about acknowledges stanzas
|
|
||||||
------------------------------------------------
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Requisting stanza acknowledgements from the server
|
|
||||||
--------------------------------------------------
|
|
||||||
|
|
||||||
### By using predicates
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### Manually
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Enable stream resumption
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
TODO
|
|
|
@ -1,12 +0,0 @@
|
||||||
Entity Time Exchange
|
|
||||||
====================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Supports a protocol that XMPP clients use to exchange their respective local
|
|
||||||
times and time zones.
|
|
||||||
|
|
||||||
**XEP related:** [XEP-90](http://www.xmpp.org/extensions/xep-0090.html)
|
|
||||||
|
|
||||||
_More coming soon._
|
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
XHTML Messages
|
|
||||||
==============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Provides the ability to send and receive formatted messages using XHTML.
|
|
||||||
|
|
||||||
Follow these links to learn how to compose, send, receive and discover support
|
|
||||||
for XHTML messages:
|
|
||||||
|
|
||||||
* Compose an XHTML Message
|
|
||||||
* Send an XHTML Message
|
|
||||||
* Receive an XHTML Message
|
|
||||||
* Discover support for XHTML Messages
|
|
||||||
|
|
||||||
**XEP related:** [XEP-71](http://www.xmpp.org/extensions/xep-0071.html)
|
|
||||||
|
|
||||||
Compose an XHTML Message
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
The first step in order to send an XHTML message is to compose it. Smack
|
|
||||||
provides a special class that helps to build valid XHTML messages hiding any
|
|
||||||
low level complexity. For special situations, advanced users may decide not to
|
|
||||||
use the helper class and generate the XHTML by themselves. Even for these
|
|
||||||
situations Smack provides a well defined entry point in order to add the
|
|
||||||
generated XHTML content to a given message.
|
|
||||||
|
|
||||||
Note: not all clients are able to view XHTML formatted messages. Therefore,
|
|
||||||
it's recommended that you include a normal body in that message that is either
|
|
||||||
an unformatted version of the text or a note that XHTML support is required to
|
|
||||||
view the message contents.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
Create an instance of _**XHTMLText**_ specifying the style and language of the
|
|
||||||
body. You can add several XHTML bodies to the message but each body should be
|
|
||||||
for a different language. Once you have an XHTMLText you can start to append
|
|
||||||
tags and text to it. In order to append tags there are several messages that
|
|
||||||
you can use. For each XHTML defined tag there is a message that you can send.
|
|
||||||
In order to add text you can send the message **#append(String
|
|
||||||
textToAppend)**.
|
|
||||||
|
|
||||||
After you have configured the XHTML text, the last step you have to do is to
|
|
||||||
add the XHTML text to the message you want to send. If you decided to create
|
|
||||||
the XHTML text by yourself, you will have to follow this last step too. In
|
|
||||||
order to add the XHTML text to the message send the message **#addBody(Message
|
|
||||||
message, String body)** to the _**XHTMLManager**_ class where _message_ is the
|
|
||||||
message that will receive the XHTML body and _body_ is the string to add as an
|
|
||||||
XHTML body to the message.**
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how to compose the following XHTML message:
|
|
||||||
|
|
||||||
```
|
|
||||||
<body>
|
|
||||||
<p style='font-size:large'>Hey John, this is my new
|
|
||||||
<span style='color:green'>green</span>
|
|
||||||
<em>!!!!</em>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a message to send
|
|
||||||
Message msg = chat.createMessage();
|
|
||||||
msg.setSubject("Any subject you want");
|
|
||||||
msg.setBody("Hey John, this is my new green!!!!");
|
|
||||||
|
|
||||||
// Create an XHTMLText to send with the message
|
|
||||||
XHTMLText xhtmlText = new XHTMLText(null, null);
|
|
||||||
xhtmlText.appendOpenParagraphTag("font-size:large");
|
|
||||||
xhtmlText.append("Hey John, this is my new ");
|
|
||||||
xhtmlText.appendOpenSpanTag("color:green");
|
|
||||||
xhtmlText.append("green");
|
|
||||||
xhtmlText.appendCloseSpanTag();
|
|
||||||
xhtmlText.appendOpenEmTag();
|
|
||||||
xhtmlText.append("!!!!");
|
|
||||||
xhtmlText.appendCloseEmTag();
|
|
||||||
xhtmlText.appendCloseParagraphTag();
|
|
||||||
xhtmlText.appendCloseBodyTag();
|
|
||||||
|
|
||||||
// Add the XHTML text to the message
|
|
||||||
XHTMLManager.addBody(msg, xhtmlText);
|
|
||||||
```
|
|
||||||
|
|
||||||
Send an XHTML Message
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
After you have composed an XHTML message you will want to send it. Once you
|
|
||||||
have added the XHTML content to the message you want to send you are almost
|
|
||||||
done. The last step is to send the message as you do with any other message.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
An XHTML message is like any regular message, therefore to send the message
|
|
||||||
you can follow the usual steps you do in order to send a message. For example,
|
|
||||||
to send a message as part of a chat just use the message **#sendMessage(Message)** of
|
|
||||||
_**Chat**_ or you can use the message **#sendStanza(Stanza)** of
|
|
||||||
_**XMPPConnection**_.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how to send a message with XHTML content as part of
|
|
||||||
a chat.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a message to send
|
|
||||||
Message msg = chat.createMessage();
|
|
||||||
// Obtain the XHTML text to send from somewhere
|
|
||||||
XHTMLText xhtmlBody = getXHTMLTextToSend();
|
|
||||||
|
|
||||||
// Add the XHTML text to the message
|
|
||||||
XHTMLManager.addBody(msg, xhtmlBody);
|
|
||||||
|
|
||||||
// Send the message that contains the XHTML
|
|
||||||
chat.sendMessage(msg);
|
|
||||||
```
|
|
||||||
|
|
||||||
Receive an XHTML Message
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
It is also possible to obtain the XHTML content from a received message.
|
|
||||||
Remember that the specification defines that a message may contain several
|
|
||||||
XHTML bodies where each body should be for a different language.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
To get the XHTML bodies of a given message just send the message
|
|
||||||
**#getBodies(Message)** to the class _**XHTMLManager**_. The answer of this
|
|
||||||
message will be an _**List**_ with the different XHTML bodies of the
|
|
||||||
message or null if none.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how to create a PacketListener that obtains the
|
|
||||||
XHTML bodies of any received message.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a listener for the chat and display any XHTML content
|
|
||||||
IncomingChatMessageListener listener = new IncomingChatMessageListener() {
|
|
||||||
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
|
||||||
// Obtain the XHTML bodies of the message
|
|
||||||
List<CharSequence> bodies = XHTMLManager.getBodies(message);
|
|
||||||
if (bodies == null) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Display the bodies on the console
|
|
||||||
for (CharSequence body : bodies) {
|
|
||||||
System.out.println(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
chatManager.addListener(listener);
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover support for XHTML Messages
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
**Description**
|
|
||||||
|
|
||||||
Before you start to send XHTML messages to a user you should discover if the
|
|
||||||
user supports XHTML messages. There are two ways to achieve the discovery,
|
|
||||||
explicitly and implicitly. Explicit is when you first try to discover if the
|
|
||||||
user supports XHTML before sending any XHTML message. Implicit is when you
|
|
||||||
send XHTML messages without first discovering if the conversation partner's
|
|
||||||
client supports XHTML and depenging on the answer (normal message or XHTML
|
|
||||||
message) you find out if the user supports XHTML messages or not. This section
|
|
||||||
explains how to explicitly discover for XHTML support.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
In order to discover if a remote user supports XHTML messages send
|
|
||||||
**#isServiceEnabled(XMPPConnection connection, String userID)** to the class
|
|
||||||
_**XHTMLManager**_ where connection is the connection to use to perform the
|
|
||||||
service discovery and userID is the user to check (A fully qualified xmpp ID,
|
|
||||||
e.g. jdoe@example.com). This message will return true if the specified user
|
|
||||||
handles XHTML messages.
|
|
||||||
|
|
||||||
**Example**
|
|
||||||
|
|
||||||
In this example we can see how to discover if a remote user supports XHTML
|
|
||||||
Messages.
|
|
||||||
|
|
||||||
```
|
|
||||||
Message msg = chat.createMessage();
|
|
||||||
// Include a normal body in the message
|
|
||||||
msg.setBody(getTextToSend());
|
|
||||||
// Check if the other user supports XHTML messages
|
|
||||||
if (XHTMLManager.isServiceEnabled(connection, chat.getParticipant())) {
|
|
||||||
// Obtain the XHTML text to send from somewhere
|
|
||||||
String xhtmlBody = getXHTMLTextToSend();
|
|
||||||
// Include an XHTML body in the message
|
|
||||||
qHTMLManager.addBody(msg, xhtmlBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the message
|
|
||||||
chat.sendMessage(msg);
|
|
||||||
```
|
|
|
@ -1,119 +0,0 @@
|
||||||
Smack: Getting Started
|
|
||||||
======================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
This document will introduce you to the Smack API and provide an overview of
|
|
||||||
important classes and concepts.
|
|
||||||
|
|
||||||
Smack Modules and Requirements
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Smack is meant to be easily embedded into any existing Java application. The
|
|
||||||
library ships as several modules to provide more flexibility over which
|
|
||||||
features applications require:
|
|
||||||
|
|
||||||
* `smack-core` -- provides core XMPP functionality. All XMPP features that are part of the XMPP RFCs are included.
|
|
||||||
* `smack-im` -- provides functionality defined in RFC 6121 (XMPP-IM), like the Roster.
|
|
||||||
* `smack-tcp` -- support for XMPP over TCP. Includes XMPPTCPConnection class, which you usually want to use
|
|
||||||
* `smack-extensions` -- support for many of the extensions (XEPs) defined by the XMPP Standards Foundation, including multi-user chat, file transfer, user search, etc. The extensions are documented in the [extensions manual](extensions/index.md).
|
|
||||||
* `smack-experimental` -- support for experimental extensions (XEPs) defined by the XMPP Standards Foundation. The API and functionality of those extensions should be considered as unstable.
|
|
||||||
* `smack-legacy` -- support for legacy extensions (XEPs) defined by the XMPP Standards Foundation.
|
|
||||||
* `smack-bosh` -- support for BOSH (XEP-0124). This code should be considered as beta.
|
|
||||||
* `smack-resolver-minidns` -- support for resolving DNS SRV records with the help of MiniDNS. Ideal for platforms that do not support the javax.naming API. Also supports [DNSSEC](dnssec.md).
|
|
||||||
* `smack-resolver-dnsjava` -- support for resolving DNS SRV records with the help of dnsjava.
|
|
||||||
* `smack-resolver-javax` -- support for resolving DNS SRV records with the javax namespace API.
|
|
||||||
* `smack-debug` -- an enhanced GUI debugger for protocol traffic. It will automatically be used when found in the classpath and when [debugging](debugging.md) is enabled.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Smack has an initialization process that involves 2 phases.
|
|
||||||
|
|
||||||
* Initializing system properties - Initializing all the system properties accessible through the class **SmackConfiguration**. These properties are retrieved by the _getXXX_ methods on that class.
|
|
||||||
* Initializing startup classes - Initializing any classes meant to be active at startup by instantiating the class, and then calling the _initialize_ method on that class if it extends **SmackInitializer**. If it does not extend this interface, then initialization will have to take place in a static block of code which is automatically executed when the class is loaded.
|
|
||||||
|
|
||||||
Initialization is accomplished via a configuration file. By default, Smack
|
|
||||||
will load the one embedded in the Smack jar at _org.jivesoftware.smack/smack-
|
|
||||||
config.xml_. This particular configuration contains a list of initializer
|
|
||||||
classes to load. All manager type classes that need to be initialized are
|
|
||||||
contained in this list of initializers.
|
|
||||||
|
|
||||||
Establishing a Connection
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The `XMPPTCPConnection` class is used to create a connection to an XMPP
|
|
||||||
server. Below are code examples for making a connection:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a connection and login to the example.org XMPP service.
|
|
||||||
AbstractXMPPConnection conn1 = new XMPPTCPConnection("username", "password", "example.org");
|
|
||||||
conn1.connect().login();
|
|
||||||
```
|
|
||||||
|
|
||||||
Further connection parameters can be configured by using a configuration builder:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a connection to the jabber.org server on a specific port.
|
|
||||||
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
|
|
||||||
.setUsernameAndPassword("username", "password")
|
|
||||||
.setXmppDomain("jabber.org")
|
|
||||||
.setHost("earl.jabber.org")
|
|
||||||
.setPort(8222)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
AbstractXMPPConnection conn2 = new XMPPTCPConnection(config);
|
|
||||||
conn2.connect().login();
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that maximum security will be used when connecting to the server by
|
|
||||||
default (and when possible), including use of TLS encryption. The
|
|
||||||
ConnectionConfiguration class provides advanced control over the connection
|
|
||||||
created, such as the ability to disable or require encryption. See
|
|
||||||
[XMPPConnection Management](connections.md) for full details.
|
|
||||||
|
|
||||||
Once you've created a connection, you should login with the
|
|
||||||
`XMPPConnection.login()` method. Once you've logged in, you can begin
|
|
||||||
chatting with other users by creating new `Chat` or `MultiUserChat`
|
|
||||||
objects.
|
|
||||||
|
|
||||||
Working with the Roster
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
The roster lets you keep track of the availability (presence) of other users.
|
|
||||||
Users can be organized into groups such as "Friends" and "Co-workers", and
|
|
||||||
then you discover whether each user is online or offline.
|
|
||||||
|
|
||||||
Retrieve the roster using the `Roster.getInstanceFor(XMPPConnection)` method. The roster
|
|
||||||
class allows you to find all the roster entries, the groups they belong to,
|
|
||||||
and the current presence status of each entry.
|
|
||||||
|
|
||||||
Reading and Writing Stanzas
|
|
||||||
|
|
||||||
Each message to the XMPP server from a client is called a packet and is sent
|
|
||||||
as XML. The `org.jivesoftware.smack.packet` package contains classes that
|
|
||||||
encapsulate the three different basic packet types allowed by XMPP (message,
|
|
||||||
presence, and IQ). Classes such as `Chat` and `GroupChat` provide higher-level
|
|
||||||
constructs that manage creating and sending packets automatically, but you can
|
|
||||||
also create and send packets directly. Below is a code example for changing
|
|
||||||
your presence to let people know you're unavailable and "out fishing":
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a new presence. Pass in false to indicate we're unavailable._
|
|
||||||
Presence presence = new Presence(Presence.Type.unavailable);
|
|
||||||
presence.setStatus("Gone fishing");
|
|
||||||
// Send the stanza (assume we have an XMPPConnection instance called "con").
|
|
||||||
con.sendStanza(presence);
|
|
||||||
```
|
|
||||||
|
|
||||||
Smack provides two ways to read incoming packets: `StanzaListener`, and
|
|
||||||
`StanzaCollector`. Both use `StanzaFilter` instances to determine which
|
|
||||||
stanzas should be processed. A stanza listener is used for event style
|
|
||||||
programming, while a stanza collector has a result queue of packets that you
|
|
||||||
can do polling and blocking operations on. So, a stanza listener is useful
|
|
||||||
when you want to take some action whenever a stanza happens to come in, while
|
|
||||||
a stanza collector is useful when you want to wait for a specific packet to
|
|
||||||
arrive. Stanza collectors and listeners can be created using an Connection
|
|
||||||
instance.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,16 +0,0 @@
|
||||||
**Smack Documentation**
|
|
||||||
|
|
||||||
**Contents:**
|
|
||||||
|
|
||||||
* [Overview](overview.md)
|
|
||||||
* [Getting Started Guide](gettingstarted.md)
|
|
||||||
* [Managing Connections](connections.md)
|
|
||||||
* [Messaging Basics](messaging.md)
|
|
||||||
* [Roster and Presence](roster.md)
|
|
||||||
* [Processing Incoming Stanzas](processing.md)
|
|
||||||
* [Provider Architecture](providers.md)
|
|
||||||
* [DNSSEC and DANE](dnssec.md)
|
|
||||||
* [Debugging with Smack](debugging.md)
|
|
||||||
|
|
||||||
* [Smack Extensions Manual](extensions/index.md)
|
|
||||||
* [Smack's Modular Connection Architecture](connection-modules.md)
|
|
|
@ -1,15 +0,0 @@
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Smack Legacy User Manual</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<frameset cols="200,*">
|
|
||||||
<frame src="toc.html" name="navFrame" target="mainFrame">
|
|
||||||
<frame src="intro.html" name="mainFrame">
|
|
||||||
</frameset>
|
|
||||||
<noframes>
|
|
||||||
<H2>Smack Legacy User Manual</H2>
|
|
||||||
|
|
||||||
<a href="toc.html">Smack Extensions User Manual</a></noframes></html>
|
|
|
@ -1,26 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Smack Extensions User Manual</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="header">Smack Legacy Manual</div>
|
|
||||||
<p>The XMPP protocol includes a base protocol and many optional extensions
|
|
||||||
typically documented as "XEP's". Smack provides the smack-legacy artifcate, with
|
|
||||||
implementations of now <b>obsolete</b> XEPs. You should no longer use this code!</p>
|
|
||||||
|
|
||||||
<div class="subheader">Legacy Extensions</div><p>
|
|
||||||
|
|
||||||
<table border="0" width="85%" cellspacing="0" cellpadding="3" style="border:1px #bbb solid;">
|
|
||||||
<tr bgcolor="#ddeeff">
|
|
||||||
<td><b>Name</b></td><td><b>XEP #</b></td><td><b>Description</b></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><a href="messageevents.html">Message Events</a></td>
|
|
||||||
<td><a href="http://www.xmpp.org/extensions/xep-0022.html">XEP-0022</a></td>
|
|
||||||
<td>Requests and responds to message events.</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,244 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Message Events</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="header">Message Events</div><p>
|
|
||||||
|
|
||||||
This extension is used to request and respond to events relating to the delivery,
|
|
||||||
display, and composition of messages. There are three stages in this extension:<ol>
|
|
||||||
<li>Request for event notifications,
|
|
||||||
<li>Receive the event notification requests and send event notifications, and
|
|
||||||
<li>Receive the event notifications.</ol>
|
|
||||||
<p>For more information on each stage please follow these links:</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#reqevnot">Requesting Event Notifications</a></li>
|
|
||||||
<li><a href="#lstevnotreq">Reacting to Event Notification Requests</a></li>
|
|
||||||
<li><a href="#lstevnot">Reacting to Event Notifications</a></li>
|
|
||||||
</ul>
|
|
||||||
<b>XEP related:</b> <a href="http://www.xmpp.org/extensions/xep-0022.html">XEP-22</a>
|
|
||||||
<hr>
|
|
||||||
<div class="subheader"><a name="reqevnot">Requesting Event Notifications</a></div><p>
|
|
||||||
<b>Description</b><p>
|
|
||||||
|
|
||||||
In order to receive event notifications for a given message you first have to specify
|
|
||||||
which events are you interested in. Each message that you send has to request its own event
|
|
||||||
notifications. Therefore, every message that you send as part of a chat should request its own event
|
|
||||||
notifications.</p>
|
|
||||||
|
|
||||||
<b>Usage</b><p>
|
|
||||||
|
|
||||||
The class <i>MessageEventManager</i> provides an easy way for requesting event notifications. All you have to do is specify
|
|
||||||
the message that requires the event notifications and the events that you are interested in.
|
|
||||||
<p>Use the static method <i><b>MessageEventManager.addNotificationsRequests(Message message, boolean offline, boolean
|
|
||||||
delivered, boolean displayed, boolean composing)</b></i> for requesting event notifications.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<b>Example</b><p>
|
|
||||||
Below you can find an example that logs in a user to the server, creates a message, adds the requests
|
|
||||||
for notifications and sends the message.
|
|
||||||
<blockquote>
|
|
||||||
<pre> <font color="#3f7f5f">// Connect to the server and log in</font>
|
|
||||||
conn1 = new XMPPConnection(host);
|
|
||||||
conn1.login(server_user1, pass1);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Create a chat with user2</font>
|
|
||||||
Chat chat1 = conn1.createChat(user2);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Create a message to send</font>
|
|
||||||
Message msg = chat1.createMessage();
|
|
||||||
msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
|
|
||||||
msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
|
|
||||||
<font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font>
|
|
||||||
<font color="#3f7f5f">// composing)</font>
|
|
||||||
MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Send the message that contains the notifications request</font>
|
|
||||||
chat1.sendMessage(msg);
|
|
||||||
</pre>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="subheader"><a name="lstevnotreq">Reacting to Event Notification Requests</a></div><p>
|
|
||||||
|
|
||||||
<b>Description</b><p>
|
|
||||||
|
|
||||||
You can receive notification requests for the following events: delivered, displayed, composing and offline. You
|
|
||||||
<b>must</b> listen for these requests and react accordingly.</p>
|
|
||||||
|
|
||||||
<b>Usage</b><p>
|
|
||||||
|
|
||||||
The general idea is to create a new <i>DefaultMessageEventRequestListener</i> that will listen to the event notifications
|
|
||||||
requests and react with custom logic. Then you will have to add the listener to the
|
|
||||||
<i>MessageEventManager</i> that works on
|
|
||||||
the desired <i>XMPPConnection</i>.
|
|
||||||
<p>Note that <i>DefaultMessageEventRequestListener</i> is a default implementation of the
|
|
||||||
<i>MessageEventRequestListener</i> interface.
|
|
||||||
The class <i>DefaultMessageEventRequestListener</i> automatically sends a delivered notification to the sender of the message
|
|
||||||
if the sender has requested to be notified when the message is delivered. If you decide to create a new class that
|
|
||||||
implements the <i>MessageEventRequestListener</i> interface, please remember to send the delivered notification.</p>
|
|
||||||
<ul>
|
|
||||||
<li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor.
|
|
||||||
</li>
|
|
||||||
<li>To create an event notification requests listener create a subclass of <i><b>DefaultMessageEventRequestListener</b></i> or
|
|
||||||
create a class that implements the <i><b>MessageEventRequestListener</b></i> interface.
|
|
||||||
</li>
|
|
||||||
<li>To add a listener to the messageEventManager use the MessageEventManager's message
|
|
||||||
<i><b>addMessageEventRequestListener(MessageEventRequestListener)</b></i>.</li>
|
|
||||||
</ul></p>
|
|
||||||
|
|
||||||
<b>Example</b><p>
|
|
||||||
|
|
||||||
Below you can find an example that connects two users to the server. One user will create a message, add the requests
|
|
||||||
for notifications and will send the message to the other user. The other user will add a
|
|
||||||
<i>DefaultMessageEventRequestListener</i>
|
|
||||||
to a <i>MessageEventManager</i> that will listen and react to the event notification requested by the other user.
|
|
||||||
<blockquote>
|
|
||||||
<pre> <font color="#3f7f5f">// Connect to the server and log in the users</font>
|
|
||||||
conn1 = new XMPPTCPConnection(host);
|
|
||||||
conn1.login(server_user1, pass1);
|
|
||||||
conn2 = new XMPPTCPConnection(host);
|
|
||||||
conn2.login(server_user2, pass2);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// User2 creates a MessageEventManager</font>
|
|
||||||
MessageEventManager messageEventManager = new MessageEventManager(conn2);
|
|
||||||
<font color="#3f7f5f">// User2 adds the listener that will react to the event notifications requests</font>
|
|
||||||
messageEventManager.addMessageEventRequestListener(new DefaultMessageEventRequestListener() {
|
|
||||||
public void deliveredNotificationRequested(
|
|
||||||
String from,
|
|
||||||
String packetID,
|
|
||||||
MessageEventManager messageEventManager) {
|
|
||||||
super.deliveredNotificationRequested(from, packetID, messageEventManager);
|
|
||||||
<font color="#3f7f5f">// DefaultMessageEventRequestListener automatically responds that the message was delivered when receives this request</font>
|
|
||||||
System.out.println(<font color="#0000FF">"Delivered Notification Requested (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void displayedNotificationRequested(
|
|
||||||
String from,
|
|
||||||
String packetID,
|
|
||||||
MessageEventManager messageEventManager) {
|
|
||||||
super.displayedNotificationRequested(from, packetID, messageEventManager);
|
|
||||||
<font color="#3f7f5f">// Send to the message's sender that the message was displayed</font>
|
|
||||||
messageEventManager.sendDisplayedNotification(from, packetID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void composingNotificationRequested(
|
|
||||||
String from,
|
|
||||||
String packetID,
|
|
||||||
MessageEventManager messageEventManager) {
|
|
||||||
super.composingNotificationRequested(from, packetID, messageEventManager);
|
|
||||||
<font color="#3f7f5f">// Send to the message's sender that the message's receiver is composing a reply</font>
|
|
||||||
messageEventManager.sendComposingNotification(from, packetID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void offlineNotificationRequested(
|
|
||||||
String from,
|
|
||||||
String packetID,
|
|
||||||
MessageEventManager messageEventManager) {
|
|
||||||
super.offlineNotificationRequested(from, packetID, messageEventManager);
|
|
||||||
<font color="#3f7f5f">// The XMPP server should take care of this request. Do nothing.</font>
|
|
||||||
System.out.println(<font color="#0000FF">"Offline Notification Requested (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// User1 creates a chat with user2</font>
|
|
||||||
Chat chat1 = conn1.createChat(user2);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// User1 creates a message to send to user2</font>
|
|
||||||
Message msg = chat1.createMessage();
|
|
||||||
msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
|
|
||||||
msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
|
|
||||||
<font color="#3f7f5f">// User1 adds to the message all the notifications requests (offline, delivered, displayed,</font>
|
|
||||||
<font color="#3f7f5f">// composing)</font>
|
|
||||||
MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// User1 sends the message that contains the notifications request</font>
|
|
||||||
chat1.sendMessage(msg);
|
|
||||||
Thread.sleep(500);
|
|
||||||
<font color="#3f7f5f">// User2 sends to the message's sender that the message's receiver cancelled composing a reply</font>
|
|
||||||
messageEventManager.sendCancelledNotification(user1, msg.getPacketID());
|
|
||||||
</pre>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="subheader"><a name="lstevnot">Reacting to Event Notifications</a></div><p>
|
|
||||||
|
|
||||||
<b>Description</b><p>
|
|
||||||
|
|
||||||
Once you have requested for event notifications you will start to receive notifications of events. You can
|
|
||||||
receive notifications of the following events: delivered, displayed, composing, offline and cancelled. You
|
|
||||||
will probably want to react to some or all of these events.</p>
|
|
||||||
|
|
||||||
<b>Usage</b><p>
|
|
||||||
|
|
||||||
The general idea is to create a new <i>MessageEventNotificationListener</i> that will listen to the event notifications
|
|
||||||
and react with custom logic. Then you will have to add the listener to the <i>MessageEventManager</i> that works on
|
|
||||||
the desired <i>XMPPConnection</i>.
|
|
||||||
<ul>
|
|
||||||
<li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor.
|
|
||||||
</li>
|
|
||||||
<li>To create an event notifications listener create a class that implements the <i><b>MessageEventNotificationListener</b></i>
|
|
||||||
interface.
|
|
||||||
</li>
|
|
||||||
<li>To add a listener to the messageEventManager use the MessageEventManager's message
|
|
||||||
<i><b>addMessageEventNotificationListener(MessageEventNotificationListener)</b></i>.</li>
|
|
||||||
</ul></p>
|
|
||||||
|
|
||||||
<b>Example</b><p>
|
|
||||||
Below you can find an example that logs in a user to the server, adds a <i>MessageEventNotificationListener</i>
|
|
||||||
to a <i>MessageEventManager</i> that will listen and react to the event notifications, creates a message, adds
|
|
||||||
the requests for notifications and sends the message.
|
|
||||||
<blockquote>
|
|
||||||
<pre> <font color="#3f7f5f">// Connect to the server and log in</font>
|
|
||||||
conn1 = new XMPPTCPConnection(host);
|
|
||||||
conn1.login(server_user1, pass1);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Create a MessageEventManager</font>
|
|
||||||
MessageEventManager messageEventManager = new MessageEventManager(conn1);
|
|
||||||
<font color="#3f7f5f">// Add the listener that will react to the event notifications</font>
|
|
||||||
messageEventManager.addMessageEventNotificationListener(new MessageEventNotificationListener() {
|
|
||||||
public void deliveredNotification(String from, String packetID) {
|
|
||||||
System.out.println(<font color="#0000FF">"The message has been delivered (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void displayedNotification(String from, String packetID) {
|
|
||||||
System.out.println(<font color="#0000FF">"The message has been displayed (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void composingNotification(String from, String packetID) {
|
|
||||||
System.out.println(<font color="#0000FF">"The message's receiver is composing a reply (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void offlineNotification(String from, String packetID) {
|
|
||||||
System.out.println(<font color="#0000FF">"The message's receiver is offline (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancelledNotification(String from, String packetID) {
|
|
||||||
System.out.println(<font color="#0000FF">"The message's receiver cancelled composing a reply (" + from + ", " + packetID + ")"</font>);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Create a chat with user2</font>
|
|
||||||
Chat chat1 = conn1.createChat(user2);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Create a message to send</font>
|
|
||||||
Message msg = chat1.createMessage();
|
|
||||||
msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
|
|
||||||
msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
|
|
||||||
<font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font>
|
|
||||||
<font color="#3f7f5f">// composing)</font>
|
|
||||||
MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
|
|
||||||
|
|
||||||
<font color="#3f7f5f">// Send the message that contains the notifications request</font>
|
|
||||||
chat1.sendMessage(msg);
|
|
||||||
</pre>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Smack Legacy User Manual</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
<base target="mainFrame">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<a href="intro.html">Introduction</a><p>
|
|
||||||
|
|
||||||
<div class="subheader">Smack Legacy Extensions</div><p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="messageevents.html">Message Events</a><br>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,60 +0,0 @@
|
||||||
Messaging using Chats
|
|
||||||
=====================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Sending messages back and forth is at the core of instant messaging. Although
|
|
||||||
individual messages can be sent and received as packets, it's generally easier
|
|
||||||
to treat the string of messages as a chat using the
|
|
||||||
`org.jivesoftware.smack.chat2.Chat` class.
|
|
||||||
|
|
||||||
Chat
|
|
||||||
----
|
|
||||||
|
|
||||||
A chat creates a new thread of messages (using a thread ID) between two users.
|
|
||||||
The following code snippet demonstrates how to create a new Chat with a user
|
|
||||||
and then send them a text message:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Assume we've created an XMPPConnection name "connection"._
|
|
||||||
ChatManager chatManager = ChatManager.getInstanceFor(connection);
|
|
||||||
chatManager.addIncomingListener(new IncomingChatMessageListener() {
|
|
||||||
@Override
|
|
||||||
void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
|
||||||
System.out.println("New message from " + from + ": " + message.getBody());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
EntityBareJid jid = JidCreate.entityBareFrom("jsmith@jivesoftware.com");
|
|
||||||
Chat chat = chatManager.chatWith(jid);
|
|
||||||
chat.send("Howdy!");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Chat.send(String)` method is a convenience method that creates a
|
|
||||||
Message object, sets the body using the String parameter, then sends the
|
|
||||||
message. In the case that you wish to set additional values on a Message
|
|
||||||
before sending it, use the
|
|
||||||
`Chat.send(Message)` method, as in the following code snippet:
|
|
||||||
|
|
||||||
```
|
|
||||||
Message newMessage = new Message();
|
|
||||||
newMessage.setBody("Howdy!");
|
|
||||||
// Additional modifications to the message Stanza.
|
|
||||||
JivePropertiesManager.addProperty(newMessage, "favoriteColor", "red");
|
|
||||||
chat.send(newMessage);
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll also notice in the example above that we specified an IncomingChatMessageListener.
|
|
||||||
The listener is notified any time a new chat message arrives.
|
|
||||||
The following code snippet uses the listener
|
|
||||||
as a parrot-bot -- it echoes back everything the other user types.
|
|
||||||
|
|
||||||
```
|
|
||||||
// Assume a IncomingChatMessageListener we've setup with a ChatManager
|
|
||||||
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
|
||||||
// Send back the same text the other user sent us.
|
|
||||||
chat.send(message.getBody());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,46 +0,0 @@
|
||||||
Smack Overview
|
|
||||||
==============
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Smack is a library for communicating with XMPP servers to perform real-time
|
|
||||||
communications, including instant messaging and group chat.
|
|
||||||
|
|
||||||
Smack Key Advantages
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
* Extremely simple to use, yet powerful API. Sending a text message to a user can be accomplished in only a few lines of code:
|
|
||||||
|
|
||||||
```java
|
|
||||||
AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
|
|
||||||
connection.connect().login();
|
|
||||||
|
|
||||||
Message message = connection.getStanzaFactory()
|
|
||||||
.buildMessageStanza()
|
|
||||||
.to("jsmith@jivesoftware.com")
|
|
||||||
.setBody("Howdy! How are you?")
|
|
||||||
.build();
|
|
||||||
connection.sendStanza(message);
|
|
||||||
```
|
|
||||||
|
|
||||||
* Doesn't force you to code at the XMPP protocol level, as other libraries do. Smack provides intelligent higher level constructs such as the `Chat` and `Roster` classes, which let you program more efficiently.
|
|
||||||
* Does not require that you're familiar with the XMPP XML format, or even that you're familiar with XML.
|
|
||||||
* Provides easy machine to machine communication. Smack lets you set any number of properties on each message, including properties that are Java objects.
|
|
||||||
* Open Source under the Apache License 2.0, which means you can incorporate Smack into your commercial or non-commercial applications.
|
|
||||||
|
|
||||||
About XMPP
|
|
||||||
----------
|
|
||||||
|
|
||||||
XMPP (eXtensible Messaging and Presence Protocol) is an open protocol
|
|
||||||
standardized by the IETF and supported and extended by the XMPP Standards
|
|
||||||
Foundation (XSF, [http://www.xmpp.org](http://www.xmpp.org)).
|
|
||||||
|
|
||||||
How To Use This Documentation
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
This documentation assumes that you're already familiar with the main features
|
|
||||||
of XMPP instant messaging. It's also highly recommended that you open the
|
|
||||||
Javadoc API guide and use that as a reference while reading through this
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,60 +0,0 @@
|
||||||
Processing Incoming Stanzas
|
|
||||||
===========================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Smack provides a flexible framework for processing incoming stanzas using two
|
|
||||||
constructs:
|
|
||||||
|
|
||||||
* `org.jivesoftware.smack.StanzaCollector` -- a class that lets you synchronously wait for new stanzas.
|
|
||||||
* `org.jivesoftware.smack.StanzaListener` -- an interface for asynchronously notifying you of incoming stanzas. A stanza listener is used for event style programming, while a stanza collector has a result queue of stanzas that you can do polling and blocking operations on. So, a stanza listener is useful when you want to take some action whenever a stanza happens to come in, while a stanza collector is useful when you want to wait for a specific stanza to arrive. Stanza collectors and listeners can be created using an `XMPPConnection` instance.
|
|
||||||
|
|
||||||
The `org.jivesoftware.smack.filter.StanzaFilter` interface determines which
|
|
||||||
specific stanzas will be delivered to a `StanzaCollector` or `StanzaListener`.
|
|
||||||
Many pre-defined filters can be found in the `org.jivesoftware.smack.filter`
|
|
||||||
package.
|
|
||||||
|
|
||||||
The following code snippet demonstrates registering both a stanza collector
|
|
||||||
and a stanza listener:
|
|
||||||
|
|
||||||
```
|
|
||||||
// Create a stanza filter to listen for new messages from a particular
|
|
||||||
// user. We use an AndFilter to combine two other filters._
|
|
||||||
StanzaFilter filter = new AndFilter(StanzaTypeFilter.MESSAGE,
|
|
||||||
FromMatchesFilter.create(JidCreate.entityBareFrom("mary@jivesoftware.com")));
|
|
||||||
// Assume we've created an XMPPConnection named "connection".
|
|
||||||
|
|
||||||
// First, register a stanza collector using the filter we created.
|
|
||||||
StanzaCollector myCollector = connection.createStanzaCollector(filter);
|
|
||||||
// Normally, you'd do something with the collector, like wait for new packets.
|
|
||||||
|
|
||||||
// Next, create a stanza listener. We use an anonymous inner class for brevity.
|
|
||||||
StanzaListener myListener = new StanzaListener() {
|
|
||||||
public void processStanza(Stanza stanza) {
|
|
||||||
// Do something with the incoming stanza here._
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Register the listener._
|
|
||||||
connection.addAsyncStanzaListener(myListener, filter);
|
|
||||||
// or for a synchronous stanza listener use
|
|
||||||
connection.addSyncStanzaListener(myListener, filter);
|
|
||||||
```
|
|
||||||
|
|
||||||
Standard Stanza Filters
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
A rich set of stanza filters are included with Smack, or you can create your
|
|
||||||
own filters by coding to the `StanzaFilter` interface. The default set of
|
|
||||||
filters includes:
|
|
||||||
|
|
||||||
* `StanzaTypeFilter` -- filters for stanzas that are a particular Class type.
|
|
||||||
* `StanzaIdFilter` -- filters for stanzas with a particular packet ID.
|
|
||||||
* `ThreadFilter` -- filters for message stanzas with a particular thread ID.
|
|
||||||
* `ToMatchesFilter` -- filters for stanzas that are sent to a particular address.
|
|
||||||
* `FromMatchesFilter` -- filters for stanzas that are sent from a particular address.
|
|
||||||
* `StanzaExtensionFilter` -- filters for stanzas that have a particular stanza extension.
|
|
||||||
* `AndFilter` -- implements the logical AND operation over two filters.
|
|
||||||
* `OrFilter` -- implements the logical OR operation over two filters.
|
|
||||||
* `NotFilter` -- implements the logical NOT operation on a filter.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,350 +0,0 @@
|
||||||
Provider Architecture: Stanza Extensions and Custom IQ's
|
|
||||||
========================================================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
Introduction
|
|
||||||
------------
|
|
||||||
|
|
||||||
The Smack provider architecture is a system for plugging in custom XML parsing
|
|
||||||
of packet extensions and IQ packets. The standard [Smack
|
|
||||||
Extensions](extensions/index.md) are built using the provider architecture.
|
|
||||||
There are two types of providers:
|
|
||||||
|
|
||||||
* `IQProvider` -- parses IQ requests into Java objects.
|
|
||||||
* `Extension Provider` -- parses XML sub-documents attached to packets into ExtensionElement instances. By default, Smack only knows how to process a few standard packets and sub-packets that are in a few namespaces such as:
|
|
||||||
* jabber:iq:auth
|
|
||||||
* jabber:iq:roster
|
|
||||||
* jabber:iq:register
|
|
||||||
|
|
||||||
There are many more IQ types and extensions that are part of XMPP standards, and of course an endless number that can be added as custom extensions. To support this, an extensible parsing mechanism is provided via Smack and user build providers.
|
|
||||||
|
|
||||||
Whenever a packet extension is found in a packet, parsing will be
|
|
||||||
passed to the correct provider. Each provider must extend the
|
|
||||||
ExtensionElementProvider abstract class. Each extension provider is
|
|
||||||
responsible for parsing the raw XML stream, via the
|
|
||||||
Smack's `XmlPullParser` interface, to construct an object.
|
|
||||||
|
|
||||||
You can also create an introspection provider
|
|
||||||
(`provider.IntrospectionProvider.PacketExtensionIntrospectionProvider`). Here,
|
|
||||||
bean introspection is used to try to automatically set the properties
|
|
||||||
of the class using the values in the packet extension sub-element.
|
|
||||||
|
|
||||||
When no extension provider is registered for an element name and namespace
|
|
||||||
combination, Smack will store all top-level elements of the sub-packet in the
|
|
||||||
StandardExtensionElement object and then attach it to the packet.
|
|
||||||
|
|
||||||
Management of these providers is accomplished via the [ProviderManager]()
|
|
||||||
class. There are multiple ways to add providers to the manager.
|
|
||||||
|
|
||||||
* Call addXXProvider methods - You can call the appropriate add methods directly.
|
|
||||||
|
|
||||||
```
|
|
||||||
ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
|
|
||||||
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());
|
|
||||||
```
|
|
||||||
|
|
||||||
* Add a loader - You can add a ProviderLoader which will inject a means of loading multiple providers (both types) into the manager. This is the mechanism used by Smack to load from the Smack specific file format (via ProviderFileLoader). Implementers can provide the means to load providers from any source they wish, or simply reuse the ProviderFileLoader to load from their own provider files.
|
|
||||||
|
|
||||||
```
|
|
||||||
ProviderManager.addLoader(new ProviderFileLoader(FileUtils.getStreamForUrl("classpath:com/myco/provider/myco_custom.providers", null)));
|
|
||||||
```
|
|
||||||
|
|
||||||
* VM Argument - You can add a provider file via the VM argument _smack.provider.file_. This will load the file at the specified URL during startup when Smack initializes. This also assumes the default configuration, since it requires that the **VmArgInitializer** was part of the startup configuration.
|
|
||||||
|
|
||||||
|
|
||||||
`-Dsmack.provider.file=classpath:com/myco/provider/myco_custom.providers`
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
`-Dsmack.provider.file=file:///c:/myco/provider/myco_custom.providers`
|
|
||||||
|
|
||||||
|
|
||||||
IQ Providers
|
|
||||||
------------
|
|
||||||
|
|
||||||
The IQ provider class must extend the IQProvider abstract class. Each
|
|
||||||
IQProvider is responsible for parsing the raw XML stream to create an
|
|
||||||
IQ instance.
|
|
||||||
|
|
||||||
You can also create an introspection provider
|
|
||||||
(`provider.IntrospectionProvider.IQIntrospectionProvider`). Which
|
|
||||||
uses bean introspection to try to automatically set properties of the
|
|
||||||
IQ instance using the values found in the IQ packet XML. For example,
|
|
||||||
an XMPP time packet resembles the following:
|
|
||||||
|
|
||||||
### Introspection
|
|
||||||
|
|
||||||
_Time Stanza_
|
|
||||||
|
|
||||||
|
|
||||||
<iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
|
|
||||||
<query xmlns='jabber:iq:time'>
|
|
||||||
<utc>20020910T17:58:35</utc>
|
|
||||||
<tz>MDT</tz>
|
|
||||||
<display>Tue Sep 10 12:58:35 2002</display>
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
|
|
||||||
_Time IQ Class_
|
|
||||||
|
|
||||||
|
|
||||||
class Time extends IQ {
|
|
||||||
private Date utc;
|
|
||||||
private TimeZone timeZone;
|
|
||||||
private String display;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getChildElementXML() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUtc(String utcString) {
|
|
||||||
try {
|
|
||||||
utc = StringUtils.parseDate(utcString);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeZone(String zone) {
|
|
||||||
timeZone = TimeZone.getTimeZone(zone);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDisplay(String timeDisplay) {
|
|
||||||
display = timeDisplay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_Time Provider_
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class TimeProvider extends IQIntrospectionProvider<Time> {
|
|
||||||
|
|
||||||
public TimeProvider() {
|
|
||||||
super(Time.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The introspection service will automatically try to convert the String value
|
|
||||||
from the XML into a boolean, int, long, float, double, or Class depending on
|
|
||||||
the type the IQ instance expects.
|
|
||||||
|
|
||||||
### Custom IQProvider example
|
|
||||||
|
|
||||||
Let's assume you want to write a provider for a new, unsupported IQ in Smack.
|
|
||||||
|
|
||||||
_Custom IQ_
|
|
||||||
|
|
||||||
```
|
|
||||||
<iq type='set' from='juliet@capulet.example/balcony' to='romeo@montage.example'>
|
|
||||||
<myiq xmlns='example:iq:foo' token='secret'>
|
|
||||||
<user age='42'>John Doe</user>
|
|
||||||
<location>New York</location>
|
|
||||||
</myiq>
|
|
||||||
</iq>
|
|
||||||
```
|
|
||||||
|
|
||||||
_Custom IQ Provider_
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class MyIQProvider extends IQProvider<MyIQ> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MyIQ parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException {
|
|
||||||
// Define the data we are trying to collect with sane defaults
|
|
||||||
int age = -1;
|
|
||||||
String user = null;
|
|
||||||
String location = null;
|
|
||||||
|
|
||||||
// Start parsing loop
|
|
||||||
outerloop: while(true) {
|
|
||||||
XmlPullParser.Event eventType = parser.next();
|
|
||||||
switch(eventType) {
|
|
||||||
case START_ELEMENT:
|
|
||||||
String elementName = parser.getName();
|
|
||||||
switch (elementName) {
|
|
||||||
case "user":
|
|
||||||
age = ParserUtils.getIntegerAttribute(parser, "age");
|
|
||||||
user = parser.nextText();
|
|
||||||
break;
|
|
||||||
case "location"
|
|
||||||
location = parser.nextText();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case END_ELEMENT:
|
|
||||||
// Abort condition: if the are on a end tag (closing element) of the same depth
|
|
||||||
if (parser.getDepth() == initialDepth) {
|
|
||||||
break outerloop;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the IQ instance at the end of parsing, when all data has been collected
|
|
||||||
return new MyIQ(user, age, location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### DiscoItemsProvider
|
|
||||||
|
|
||||||
_Disco Items Stanza_
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<iq type='result' from='shakespeare.lit' to='romeo@montague.net/orchard' id='items1'>
|
|
||||||
<query xmlns='http://jabber.org/protocol/disco#items'>
|
|
||||||
<item jid='people.shakespeare.lit' name='Directory of Characters'/>
|
|
||||||
<item jid='plays.shakespeare.lit' name='Play-Specific Chatrooms'/>
|
|
||||||
<item jid='mim.shakespeare.lit' name='Gateway to Marlowe IM'/>
|
|
||||||
<item jid='words.shakespeare.lit' name='Shakespearean Lexicon'/>
|
|
||||||
<item jid='globe.shakespeare.lit' name='Calendar of Performances'/>
|
|
||||||
<item jid='headlines.shakespeare.lit' name='Latest Shakespearean News'/>
|
|
||||||
<item jid='catalog.shakespeare.lit' name='Buy Shakespeare Stuff!'/>
|
|
||||||
<item jid='en2fr.shakespeare.lit' name='French Translation Service'/>
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
|
|
||||||
_Disco Items IQProvider_
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class DiscoverItemsProvider implements IQProvider<DiscoverItems> {
|
|
||||||
|
|
||||||
public DiscoverItems parseIQ(XmlPullParser parser, int initialDepth) throw XmlPullParserException, IOException {
|
|
||||||
DiscoverItems discoverItems = new DiscoverItems();
|
|
||||||
DiscoverItems.Item item;
|
|
||||||
String jid = "";
|
|
||||||
String name = "";
|
|
||||||
String action = "";
|
|
||||||
String node = "";
|
|
||||||
discoverItems.setNode(parser.getAttributeValue("", "node"));
|
|
||||||
outerloop: while (true) {
|
|
||||||
XmlPullParser.Event eventType = parser.next();
|
|
||||||
switch (eventType) {
|
|
||||||
case START_ELEMENT:
|
|
||||||
String elementName = parser.getName();
|
|
||||||
switch (elementName) {
|
|
||||||
case "item":
|
|
||||||
// Initialize the variables from the parsed XML
|
|
||||||
jid = parser.getAttributeValue("", "jid");
|
|
||||||
name = parser.getAttributeValue("", "name");
|
|
||||||
node = parser.getAttributeValue("", "node");
|
|
||||||
action = parser.getAttributeValue("", "action");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case END_ELEMENT:
|
|
||||||
String elementName = parser.getName();
|
|
||||||
switch (elementName) {
|
|
||||||
case "item":
|
|
||||||
// Create a new Item and add it to DiscoverItems.
|
|
||||||
item = new DiscoverItems.Item(jid);
|
|
||||||
item.setName(name);
|
|
||||||
item.setNode(node);
|
|
||||||
item.setAction(action);
|
|
||||||
discoverItems.addItem(item);
|
|
||||||
break;
|
|
||||||
case "query":
|
|
||||||
if (parser.getDepth() == initialDepth) {
|
|
||||||
break outerloop;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return discoverItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Extension Providers
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Stanza extension providers are responsible for parsing packet extensions,
|
|
||||||
which are child elements in a custom namespace of IQ, message and presence
|
|
||||||
packets.
|
|
||||||
|
|
||||||
_Pubsub Subscription Stanza_
|
|
||||||
|
|
||||||
|
|
||||||
<iq type='result' from='pubsub.shakespeare.lit' to='francisco@denmark.lit/barracks' id='sub1'>
|
|
||||||
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
|
||||||
<subscription node='princely_musings' jid='francisco@denmark.lit' subscription='unconfigured'>
|
|
||||||
<subscribe-options>
|
|
||||||
<required/>
|
|
||||||
</subscribe-options>
|
|
||||||
</subscription>
|
|
||||||
</pubsub>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
|
|
||||||
_Subscription PacketExtensionProvider Implementation_
|
|
||||||
|
|
||||||
|
|
||||||
public class SubscriptionProvider extends ExtensionElementProvider<ExtensionElement> {
|
|
||||||
public ExtensionElement parse(XmlPullParser parser) throws Exception {
|
|
||||||
String jid = parser.getAttributeValue(null, "jid");
|
|
||||||
String nodeId = parser.getAttributeValue(null, "node");
|
|
||||||
String subId = parser.getAttributeValue(null, "subid");
|
|
||||||
String state = parser.getAttributeValue(null, "subscription");
|
|
||||||
boolean isRequired = false;
|
|
||||||
|
|
||||||
XmlPullParser.Event tag = parser.next();
|
|
||||||
|
|
||||||
if ((tag == XmlPullParser.START_ELEMENT) && parser.getName().equals("subscribe-options")) {
|
|
||||||
tag = parser.next();
|
|
||||||
|
|
||||||
if ((tag == XmlPullParser.START_ELEMENT) && parser.getName().equals("required"))
|
|
||||||
isRequired = true;
|
|
||||||
|
|
||||||
while (parser.next() != XmlPullParser.END_ELEMENT && parser.getName() != "subscribe-options");
|
|
||||||
}
|
|
||||||
while (parser.getEventType() != XmlPullParser.END_ELEMENT) parser.next();
|
|
||||||
return new Subscription(jid, nodeId, subId, state == null ? null : Subscription.State.valueOf(state), isRequired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Provider file format
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
This is the format for a provider file which can be parsed by the
|
|
||||||
**ProviderFileLoader**.
|
|
||||||
|
|
||||||
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<smackProviders>
|
|
||||||
<iqProvider>
|
|
||||||
<elementName>query</elementName>
|
|
||||||
<namespace>jabber:iq:time</namespace>
|
|
||||||
<className>org.jivesoftware.smack.packet.Time</className>
|
|
||||||
</iqProvider>
|
|
||||||
|
|
||||||
<iqProvider>
|
|
||||||
<elementName>query</elementName>
|
|
||||||
<namespace>http://jabber.org/protocol/disco#items</namespace>
|
|
||||||
<className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className>
|
|
||||||
</iqProvider>
|
|
||||||
|
|
||||||
<extensionProvider>
|
|
||||||
<elementName>subscription</elementName>
|
|
||||||
<namespace>http://jabber.org/protocol/pubsub</namespace>
|
|
||||||
<className>org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider</className>
|
|
||||||
</extensionProvider>
|
|
||||||
</smackProviders>
|
|
||||||
|
|
||||||
Each provider is associated with an element name and a namespace. If multiple
|
|
||||||
provider entries attempt to register to handle the same namespace, the last
|
|
||||||
entry added to the **ProviderManager** will overwrite any other that was
|
|
||||||
loaded before it.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -1,101 +0,0 @@
|
||||||
Roster and Presence
|
|
||||||
===================
|
|
||||||
|
|
||||||
[Back](index.md)
|
|
||||||
|
|
||||||
The roster lets you keep track of the availability ("presence") of other
|
|
||||||
users. A roster also allows you to organize users into groups such as
|
|
||||||
"Friends" and "Co-workers". Other IM systems refer to the roster as the buddy
|
|
||||||
list, contact list, etc.
|
|
||||||
|
|
||||||
A `Roster` instance is obtained using the `Roster.getInstanceFor(XMPPConnection)` method.
|
|
||||||
|
|
||||||
A detailed descriptrion of the protcol behind the Roster and Presence
|
|
||||||
semantics can be found in [RFC
|
|
||||||
6120](https://tools.ietf.org/html/rfc6121).
|
|
||||||
|
|
||||||
Roster Entries
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Every user in a roster is represented by a RosterEntry, which consists of:
|
|
||||||
|
|
||||||
* An XMPP address, aka. JID (e.g. jsmith@example.com).
|
|
||||||
* A name you've assigned to the user (e.g. "Joe").
|
|
||||||
* The list of groups in the roster that the entry belongs to. If the roster entry belongs to no groups, it's called an "unfiled entry". The following code snippet prints all entries in the roster:
|
|
||||||
|
|
||||||
```
|
|
||||||
Roster roster = Roster.getInstanceFor(connection);
|
|
||||||
Collection<RosterEntry> entries = roster.getEntries();
|
|
||||||
for (RosterEntry entry : entries) {
|
|
||||||
System.out.println(entry);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Methods also exist to get individual entries, the list of unfiled entries, or
|
|
||||||
to get one or all roster groups.
|
|
||||||
|
|
||||||
Presence
|
|
||||||
|
|
||||||
![Roster](images/roster.png)
|
|
||||||
|
|
||||||
Every entry in the roster has presence associated with it. The
|
|
||||||
`Roster.getPresence(BareJid user)` method will return a Presence object with
|
|
||||||
the user's presence or `null` if the user is not online or you are not
|
|
||||||
subscribed to the user's presence. _Note:_ Presence subscription is
|
|
||||||
nnot tied to the user being on the roster, and vice versa: You could
|
|
||||||
be subscriped to a remote users presence without the user in your roster, and
|
|
||||||
a remote user can be in your roster without any presence subscription relation.
|
|
||||||
|
|
||||||
A user either has a presence of online or offline. When a user is online,
|
|
||||||
their presence may contain extended information such as what they are
|
|
||||||
currently doing, whether they wish to be disturbed, etc. See the Presence
|
|
||||||
class for further details.
|
|
||||||
|
|
||||||
Listening for Roster and Presence Changes
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
The typical use of the roster class is to display a tree view of groups and
|
|
||||||
entries along with the current presence value of each entry. As an example,
|
|
||||||
see the image showing a Roster in the Exodus XMPP client to the right.
|
|
||||||
|
|
||||||
The presence information will likely change often, and it's also possible for
|
|
||||||
the roster entries to change or be deleted. To listen for changing roster and
|
|
||||||
presence data, a RosterListener should be used. To be informed about all
|
|
||||||
changes to the roster the RosterListener should be registered before logging
|
|
||||||
into the XMPP server. The following code snippet registers a RosterListener
|
|
||||||
with the Roster that prints any presence changes in the roster to standard
|
|
||||||
out. A normal client would use similar code to update the roster UI with the
|
|
||||||
changing information.
|
|
||||||
|
|
||||||
```
|
|
||||||
Roster roster = Roster.getInstanceFor(con);
|
|
||||||
roster.addRosterListener(new RosterListener() {
|
|
||||||
// Ignored events public void entriesAdded(Collection<String> addresses) {}
|
|
||||||
public void entriesDeleted(Collection<String> addresses) {}
|
|
||||||
public void entriesUpdated(Collection<String> addresses) {}
|
|
||||||
public void presenceChanged(Presence presence) {
|
|
||||||
System.out.println("Presence changed: " + presence.getFrom() + " " + presence);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that in order to receive presence changed events you need to be subscribed
|
|
||||||
to the users presence. See the following section.
|
|
||||||
|
|
||||||
Adding Entries to the Roster
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
Rosters and presence use a permissions-based model where users must give
|
|
||||||
permission before someone else can see their presence. This protects a
|
|
||||||
user's privacy by making sure that only approved users are able to view their
|
|
||||||
presence information. Therefore, when you add a new roster entry, you will not
|
|
||||||
see the presence information until the other user accepts your request.
|
|
||||||
|
|
||||||
If another user requests a presence subscription, you must accept or reject
|
|
||||||
that request. Smack handles presence subscription requests in one of three ways:
|
|
||||||
|
|
||||||
* Automatically accept all presence subscription requests.
|
|
||||||
* Automatically reject all presence subscription requests.
|
|
||||||
* Process presence subscription requests manually. The mode can be set using the `Roster.setSubscriptionMode(Roster.SubscriptionMode)` method. Simple clients normally use one of the automated subscription modes, while full-featured clients should manually process subscription requests and let the end-user accept or reject each request. If using the manual mode, a PacketListener should be registered that listens for Presence packets that have a type of `Presence.Type.subscribe`.
|
|
||||||
|
|
||||||
Copyright (C) Jive Software 2002-2008
|
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
<h1>Overview</h1>
|
<h1>Overview</h1>
|
||||||
|
|
||||||
<p>Smack is a library for client-to-server XMPP connections to perform real-time communications and data exchange. This includes, but is not limited to, instant messaging and group chat. More genericly speaking, it allows you to easily exchange data in various ways: For example fire-and-forget, publish-subscribe, between human and non-human endpoints. The use cases include M2M, IoT, and many more.</p>
|
<p>Smack is a library for client-to-server XMPP connections to perform real-time communications and data exchange. This includes, but is not limited to, instant messaging and group chat. More generically speaking, it allows you to easily exchange data in various ways: For example fire-and-forget, publish-subscribe, between human and non-human endpoints. The use cases include M2M, IoT, and many more.</p>
|
||||||
|
|
||||||
<p>Smack is a pure Java library, open-source and highly modular. It runs on Android and Java SE. The API strives to be easy to use but yet powerful.</p>
|
<p>Smack is a pure Java library, open-source and highly modular. It runs on Android and Java SE. The API strives to be easy to use but yet powerful.</p>
|
||||||
|
|
||||||
<h2>Key Advantages</h2>
|
<h2>Key Advantages</h2>
|
||||||
|
|
||||||
Smack is extremely simple to use. Sending a text message to a user can be accomplished in only a few lines of code.
|
Smack is extremely simple to use, yet provides a powerful API. Sending a text message to a user can be accomplished in only a few lines of code.
|
||||||
|
|
||||||
<pre>
|
<pre>{@code
|
||||||
AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
|
AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
|
||||||
connection.connect().login();
|
connection.connect().login();
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ Message message = connection.getStanzaFactory()
|
||||||
connection.sendStanza(message);
|
connection.sendStanza(message);
|
||||||
|
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
</pre>
|
}</pre>
|
||||||
|
|
||||||
<p>Smack doesn't force you to code at the protcol level of XMPP. The library provides intelligent higher level constructs, often called {@link org.jivesoftware.smack.Manager}, which let you program more efficiently. Other examples of those constructs are the Chat and Roster classes.</p>
|
<p>Smack doesn't force you to code at the protcol level of XMPP. The library provides intelligent higher level constructs, often called {@link org.jivesoftware.smack.Manager}, which let you program more efficiently. Other examples of those constructs are the Chat and Roster classes.</p>
|
||||||
|
|
||||||
|
@ -48,4 +48,74 @@ connection.disconnect();
|
||||||
|
|
||||||
<p>XMPP (eXtensible Messaging and Presence Protocol) is an open protocol standardized by the <a href="https://ietf.org/">Internet Engineering Task Force (IETF)</a> and supported and extended by the <a href="https://www.xmpp.org/">XMPP Standards Foundation (XSF)</a>.</p>
|
<p>XMPP (eXtensible Messaging and Presence Protocol) is an open protocol standardized by the <a href="https://ietf.org/">Internet Engineering Task Force (IETF)</a> and supported and extended by the <a href="https://www.xmpp.org/">XMPP Standards Foundation (XSF)</a>.</p>
|
||||||
|
|
||||||
|
<h2>Smack Modules</h2>
|
||||||
|
|
||||||
|
<p>Smack is meant to be easily embedded into any existing Java
|
||||||
|
application. The library ships as several modules to provide more
|
||||||
|
flexibility over which features applications require.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>smack-core -- provides core XMPP functionality. All XMPP features that are part of the XMPP RFCs are included.</li>
|
||||||
|
<li>smack-im -- provides functionality defined in RFC 6121 (XMPP-IM), like the Roster.</li>
|
||||||
|
<li>{@link org.jivesoftware.smack.tcp smack-tcp} -- support for XMPP over TCP. Includes XMPPTCPConnection class, which you usually want to use</li>
|
||||||
|
<li>smack-extensions -- support for many of the extensions (XEPs) defined by the XMPP Standards Foundation, including multi-user chat, file transfer, user search, etc. The extensions are documented in the [extensions manual](extensions/index.md).</li>
|
||||||
|
<li>smack-experimental -- support for experimental extensions (XEPs) defined by the XMPP Standards Foundation. The API and functionality of those extensions should be considered as unstable.</li>
|
||||||
|
<li>smack-legacy -- support for legacy extensions (XEPs) defined by the XMPP Standards Foundation.
|
||||||
|
<li>smack-bosh -- support for BOSH (XEP-0124). This code should be considered as beta.</li>
|
||||||
|
<li>smack-resolver-minidns -- support for resolving DNS SRV records with the help of <a href="https://minidns.org">MiniDNS</a>. Ideal for platforms that do not support the javax.naming API. Also supports [DNSSEC](dnssec.md) TODO: Fix link.</li>
|
||||||
|
<li>smack-resolver-dnsjava -- support for resolving DNS SRV records with the help of dnsjava.</li>
|
||||||
|
<li>smack-resolver-javax -- support for resolving DNS SRV records with the javax namespace API.</li>
|
||||||
|
<li>smack-debug -- an enhanced GUI debugger for protocol traffic. It will automatically be used when found in the classpath and when [debugging](debugging.md) is enabled.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Main API Entry Points</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{@link org.jivesoftware.smack.XMPPConnection}</li>
|
||||||
|
<li>{@link org.jivesoftware.smack.tcp.XMPPTCPConnection}</li>
|
||||||
|
<li>{@link org.jivesoftware.smack.roster.Roster}</li>
|
||||||
|
<!-- <li>{@link org.jivesoftware.smack.chat2.Chat}</li> -->
|
||||||
|
<!-- More form index.md -->
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Smack Extensions</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Since the X in XMPP stands for eXstensible, Smack comes with many
|
||||||
|
built-in extensions for XMPP. Click
|
||||||
|
</p>
|
||||||
|
<blockquote><b>{@link org.jivesoftware.smackx}</b></blockquote>
|
||||||
|
<p>
|
||||||
|
for an overview of all supporteted XMPP extensions of Smack.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Some selected extensions are
|
||||||
|
<ul>
|
||||||
|
<li>{@link org.jivesoftware.smackx.muc.MultiUserChat Multi-User Chat (XEP-0045)}</li>
|
||||||
|
<li>{@link org.jivesoftware.smackx.carbons.CarbonManager Message Carbons (XEP-0289)}</li>
|
||||||
|
<li>{@link org.jivesoftware.smackx.omemo OMEMO (XEP-0384)}</li>
|
||||||
|
<!-- <li>{@link org.jivesoftware.smack.chat2.Chat}</li> -->
|
||||||
|
<!-- More form index.md -->
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
|
||||||
|
<p>Smack has an initialization process that involves 2 phases.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Initializing system properties - Initializing all the system properties accessible through the class **SmackConfiguration**. These properties are retrieved by the _getXXX_ methods on that class.</li>
|
||||||
|
<li>Initializing startup classes - Initializing any classes meant to be active at startup by instantiating the class, and then calling the _initialize_ method on that class if it extends **SmackInitializer**. If it does not extend this interface, then initialization will have to take place in a static block of code which is automatically executed when the class is loaded.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Initialization is accomplished via a configuration file.
|
||||||
|
By default, Smack
|
||||||
|
will load the one embedded in the Smack jar at _org.jivesoftware.smack/smack-
|
||||||
|
config.xml_.
|
||||||
|
This particular configuration contains a list of initializer
|
||||||
|
classes to load.
|
||||||
|
All manager type classes that need to be initialized are
|
||||||
|
contained in this list of initializers.
|
||||||
|
</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -1 +1 @@
|
||||||
../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/package-info.java
|
../../../../../../../smack-java8-full/src/main/java/org/jivesoftware/smackx/package-info.java
|
|
@ -1 +1 @@
|
||||||
../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/package-info.java
|
../../../../../../../smack-java8-full/src/main/java/org/jivesoftware/smackx/package-info.java
|
|
@ -47,11 +47,15 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
https = builder.https;
|
https = builder.https;
|
||||||
|
if (builder.file != null) {
|
||||||
if (builder.file.charAt(0) != '/') {
|
if (builder.file.charAt(0) != '/') {
|
||||||
file = '/' + builder.file;
|
file = '/' + builder.file;
|
||||||
} else {
|
} else {
|
||||||
file = builder.file;
|
file = builder.file;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
file = null;
|
||||||
|
}
|
||||||
httpHeaders = builder.httpHeaders;
|
httpHeaders = builder.httpHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +81,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getURI() throws URISyntaxException {
|
public URI getURI() throws URISyntaxException {
|
||||||
String uri = (https ? "https://" : "http://") + getHostString() + ":" + this.port + file;
|
String uri = (https ? "https://" : "http://") + getHostString() + ":" + this.port + (file != null ? file : "");
|
||||||
return new URI(uri);
|
return new URI(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
||||||
import java.io.PipedReader;
|
import java.io.PipedReader;
|
||||||
import java.io.PipedWriter;
|
import java.io.PipedWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -29,17 +30,19 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.GenericConnectionException;
|
import org.jivesoftware.smack.SmackException.GenericConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||||
import org.jivesoftware.smack.packet.Element;
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StanzaError;
|
import org.jivesoftware.smack.packet.StanzaError;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||||
|
import org.jivesoftware.smack.util.Async;
|
||||||
import org.jivesoftware.smack.util.CloseableUtil;
|
import org.jivesoftware.smack.util.CloseableUtil;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
@ -90,6 +93,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
@SuppressWarnings("HidingField")
|
@SuppressWarnings("HidingField")
|
||||||
private final BOSHConfiguration config;
|
private final BOSHConfiguration config;
|
||||||
|
|
||||||
|
private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingQueue = new ArrayBlockingQueueWithShutdown<>(100, true);
|
||||||
|
|
||||||
|
private Thread writerThread;
|
||||||
|
|
||||||
// Some flags which provides some info about the current state.
|
// Some flags which provides some info about the current state.
|
||||||
private boolean isFirstInitialization = true;
|
private boolean isFirstInitialization = true;
|
||||||
private boolean done = false;
|
private boolean done = false;
|
||||||
|
@ -194,11 +201,16 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert writerThread == null || !writerThread.isAlive();
|
||||||
|
outgoingQueue.start();
|
||||||
|
writerThread = Async.go(this::writeElements, this + " Writer");
|
||||||
|
|
||||||
// If there is no feedback, throw an remote server timeout error
|
// If there is no feedback, throw an remote server timeout error
|
||||||
if (!connected && !done) {
|
if (!connected && !done) {
|
||||||
done = true;
|
done = true;
|
||||||
String errorMessage = "Timeout reached for the connection to "
|
String errorMessage = "Timeout reached for the connection to "
|
||||||
+ getHost() + ":" + getPort() + ".";
|
+ getHost() + ":" + getPort() + ".";
|
||||||
|
instantShutdown();
|
||||||
throw new SmackException.SmackMessageException(errorMessage);
|
throw new SmackException.SmackMessageException(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,14 +219,14 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
|
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
|
||||||
onStreamOpen(parser);
|
onStreamOpen(parser);
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
instantShutdown();
|
||||||
throw new AssertionError("Failed to setup stream environment", e);
|
throw new AssertionError("Failed to setup stream environment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecureConnection() {
|
public boolean isSecureConnection() {
|
||||||
// TODO: Implement SSL usage
|
return config.isUsingHTTPS();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -234,40 +246,97 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
afterSuccessfulLogin(false);
|
afterSuccessfulLogin(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private volatile boolean writerThreadRunning;
|
||||||
public void sendNonza(Nonza element) throws NotConnectedException {
|
|
||||||
if (done) {
|
|
||||||
throw new NotConnectedException();
|
|
||||||
}
|
|
||||||
sendElement(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void writeElements() {
|
||||||
protected void sendStanzaInternal(Stanza packet) throws NotConnectedException {
|
writerThreadRunning = true;
|
||||||
sendElement(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendElement(Element element) {
|
|
||||||
try {
|
try {
|
||||||
send(ComposableBody.builder().setPayloadXML(element.toXML(BOSH_URI).toString()).build());
|
while (true) {
|
||||||
|
TopLevelStreamElement element;
|
||||||
|
try {
|
||||||
|
element = outgoingQueue.take();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE,
|
||||||
|
"Writer thread exiting: Outgoing queue was shutdown as signalled by interrupted exception",
|
||||||
|
e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String xmlPayload = element.toXML(BOSH_URI).toString();
|
||||||
|
ComposableBody.Builder composableBodyBuilder = ComposableBody.builder().setPayloadXML(xmlPayload);
|
||||||
|
if (sessionID != null) {
|
||||||
|
BodyQName qName = BodyQName.create(BOSH_URI, "sid");
|
||||||
|
composableBodyBuilder.setAttribute(qName, sessionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComposableBody composableBody = composableBodyBuilder.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.send(composableBody);
|
||||||
|
} catch (BOSHException e) {
|
||||||
|
LOGGER.log(Level.WARNING, this + " received BOSHException in writer thread, connection broke!", e);
|
||||||
|
// TODO: Signal the user that there was an unexpected exception.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (element instanceof Stanza) {
|
if (element instanceof Stanza) {
|
||||||
firePacketSendingListeners((Stanza) element);
|
Stanza stanza = (Stanza) element;
|
||||||
|
firePacketSendingListeners(stanza);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (BOSHException e) {
|
} catch (Exception exception) {
|
||||||
LOGGER.log(Level.SEVERE, "BOSHException in sendStanzaInternal", e);
|
LOGGER.log(Level.WARNING, "BOSH writer thread threw", exception);
|
||||||
|
} finally {
|
||||||
|
writerThreadRunning = false;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Closes the connection by setting presence to unavailable and closing the
|
protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
||||||
* HTTP client. The shutdown logic will be used during a planned disconnection or when
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
* dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's
|
try {
|
||||||
* BOSH stanza reader will not be removed; thus connection's state is kept.
|
outgoingQueue.put(element);
|
||||||
*
|
} catch (InterruptedException e) {
|
||||||
*/
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
// If the method above did not throw, then the sending thread was interrupted
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendNonBlockingInternal(TopLevelStreamElement element)
|
||||||
|
throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
boolean enqueued = outgoingQueue.offer(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
|
instantShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void instantShutdown() {
|
||||||
|
outgoingQueue.shutdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean writerThreadTerminated = waitFor(() -> !writerThreadRunning);
|
||||||
|
if (!writerThreadTerminated) {
|
||||||
|
LOGGER.severe("Writer thread of " + this + " did not terminate timely");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE, "Interrupted while waiting for writer thread to terminate", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -275,20 +344,15 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "shutdown", e);
|
LOGGER.log(Level.WARNING, "shutdown", e);
|
||||||
}
|
}
|
||||||
client = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instantShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void instantShutdown() {
|
|
||||||
setWasAuthenticated();
|
setWasAuthenticated();
|
||||||
sessionID = null;
|
sessionID = null;
|
||||||
done = true;
|
done = true;
|
||||||
authenticated = false;
|
authenticated = false;
|
||||||
connected = false;
|
connected = false;
|
||||||
isFirstInitialization = false;
|
isFirstInitialization = false;
|
||||||
|
client = null;
|
||||||
|
|
||||||
// Close down the readers and writers.
|
// Close down the readers and writers.
|
||||||
CloseableUtil.maybeClose(readerPipe, LOGGER);
|
CloseableUtil.maybeClose(readerPipe, LOGGER);
|
||||||
|
@ -410,14 +474,15 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
|
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
|
||||||
// requires a special XML element ot be send after successful SASL authentication.
|
// requires a special XML element ot be send after successful SASL authentication.
|
||||||
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
|
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
|
||||||
ComposableBody composeableBody = ComposableBody.builder().setNamespaceDefinition("xmpp",
|
ComposableBody composeableBody = ComposableBody.builder()
|
||||||
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
|
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
||||||
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
|
.setAttribute(BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"), "true")
|
||||||
"xmpp"), "true").setAttribute(
|
.setAttribute(BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString())
|
||||||
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build();
|
.setAttribute(BodyQName.create(BOSH_URI, "sid"), sessionID)
|
||||||
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
send(composeableBody);
|
client.send(composeableBody);
|
||||||
} catch (BOSHException e) {
|
} catch (BOSHException e) {
|
||||||
// jbosh's exception API does not really match the one of Smack.
|
// jbosh's exception API does not really match the one of Smack.
|
||||||
throw new SmackException.SmackWrappedException(e);
|
throw new SmackException.SmackWrappedException(e);
|
||||||
|
|
|
@ -23,7 +23,7 @@ dependencies {
|
||||||
// 'implementation' here since there is no need to shadow it
|
// 'implementation' here since there is no need to shadow it
|
||||||
// outside of the fixtures compilation classpath. That is, no test
|
// outside of the fixtures compilation classpath. That is, no test
|
||||||
// should ever setup Bouncy Castle as security provider explicitly.
|
// should ever setup Bouncy Castle as security provider explicitly.
|
||||||
testFixturesImplementation "org.bouncycastle:bcprov-jdk15on:${bouncyCastleVersion}"
|
testFixturesImplementation "org.bouncycastle:bcprov-jdk18on:${bouncyCastleVersion}"
|
||||||
testFixturesImplementation 'org.apache.commons:commons-lang3:3.10'
|
testFixturesImplementation 'org.apache.commons:commons-lang3:3.10'
|
||||||
|
|
||||||
testFixturesApi "org.jxmpp:jxmpp-jid:$jxmppVersion:tests"
|
testFixturesApi "org.jxmpp:jxmpp-jid:$jxmppVersion:tests"
|
||||||
|
|
|
@ -256,7 +256,7 @@ public class RosterSmackTest extends SmackTestCase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. Create an unfiled entry with no name
|
* 1. Create an unfiled entry with no name
|
||||||
* 2. Check that the the entry does not belong to any group
|
* 2. Check that the entry does not belong to any group
|
||||||
* 3. Change its name and add it to a group
|
* 3. Change its name and add it to a group
|
||||||
* 4. Check that the name has been modified and that the entry belongs to a group
|
* 4. Check that the name has been modified and that the entry belongs to a group
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -33,6 +33,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -51,6 +52,7 @@ import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
||||||
|
@ -349,8 +351,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
|
|
||||||
protected static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
|
protected static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
|
||||||
|
|
||||||
protected final AsyncButOrdered<StanzaListener> inOrderListeners = new AsyncButOrdered<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The used host to establish the connection to
|
* The used host to establish the connection to
|
||||||
*/
|
*/
|
||||||
|
@ -460,8 +460,17 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isSecureConnection();
|
public abstract boolean isSecureConnection();
|
||||||
|
|
||||||
protected abstract void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException;
|
// Usually batching is a good idea. So the two
|
||||||
|
// send(Internal|NonBlockingInternal) methods below could be using
|
||||||
|
// Collection<? extends TopLevelStreamElement> as parameter type instead.
|
||||||
|
// TODO: Add "batched send" support. Note that for the non-blocking variant, this probably requires a change in
|
||||||
|
// return type, so that it is possible to signal which messages could be "send" and which not.
|
||||||
|
|
||||||
|
protected abstract void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
protected abstract void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public boolean trySendStanza(Stanza stanza) throws NotConnectedException {
|
public boolean trySendStanza(Stanza stanza) throws NotConnectedException {
|
||||||
// Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
|
// Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
|
||||||
|
@ -476,6 +485,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit)
|
public boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit)
|
||||||
throws NotConnectedException, InterruptedException {
|
throws NotConnectedException, InterruptedException {
|
||||||
|
@ -486,7 +496,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void sendNonza(Nonza element) throws NotConnectedException, InterruptedException;
|
public final void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
|
||||||
|
sendInternal(nonza);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendNonzaNonBlocking(Nonza nonza) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
sendNonBlockingInternal(nonza);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isUsingCompression();
|
public abstract boolean isUsingCompression();
|
||||||
|
@ -781,7 +798,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// Indicate that we're now authenticated.
|
// Indicate that we're now authenticated.
|
||||||
this.authenticated = true;
|
this.authenticated = true;
|
||||||
|
|
||||||
// If debugging is enabled, change the the debug window title to include the
|
// If debugging is enabled, change the debug window title to include the
|
||||||
// name we are now logged-in as.
|
// name we are now logged-in as.
|
||||||
// If DEBUG was set to true AFTER the connection was created the debugger
|
// If DEBUG was set to true AFTER the connection was created the debugger
|
||||||
// will be null
|
// will be null
|
||||||
|
@ -853,8 +870,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return stanzaFactory;
|
return stanzaFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Stanza preSendStanza(Stanza stanza) throws NotConnectedException {
|
||||||
public final void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
|
|
||||||
Objects.requireNonNull(stanza, "Stanza must not be null");
|
Objects.requireNonNull(stanza, "Stanza must not be null");
|
||||||
assert stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ;
|
assert stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ;
|
||||||
|
|
||||||
|
@ -873,7 +889,19 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
|
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
|
||||||
// the content of the stanza.
|
// the content of the stanza.
|
||||||
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
|
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
|
||||||
sendStanzaInternal(stanzaAfterInterceptors);
|
return stanzaAfterInterceptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
|
||||||
|
stanza = preSendStanza(stanza);
|
||||||
|
sendInternal(stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendStanzaNonBlocking(Stanza stanza) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
stanza = preSendStanza(stanza);
|
||||||
|
sendNonBlockingInternal(stanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1619,8 +1647,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
|
|
||||||
listenersToNotify.clear();
|
listenersToNotify.clear();
|
||||||
extractMatchingListeners(packet, recvListeners, listenersToNotify);
|
extractMatchingListeners(packet, recvListeners, listenersToNotify);
|
||||||
|
final Semaphore listenerSemaphore = new Semaphore(1 - listenersToNotify.size());
|
||||||
for (StanzaListener stanzaListener : listenersToNotify) {
|
for (StanzaListener stanzaListener : listenersToNotify) {
|
||||||
inOrderListeners.performAsyncButOrdered(stanzaListener, () -> {
|
asyncGoLimited(() -> {
|
||||||
try {
|
try {
|
||||||
stanzaListener.processStanza(packet);
|
stanzaListener.processStanza(packet);
|
||||||
}
|
}
|
||||||
|
@ -1629,9 +1658,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
LOGGER.log(Level.SEVERE, "Exception in packet listener", e);
|
LOGGER.log(Level.SEVERE, "Exception in packet listener", e);
|
||||||
|
} finally {
|
||||||
|
listenerSemaphore.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
listenerSemaphore.acquireUninterruptibly();
|
||||||
|
|
||||||
// Notify the receive listeners interested in the packet
|
// Notify the receive listeners interested in the packet
|
||||||
listenersToNotify.clear();
|
listenersToNotify.clear();
|
||||||
|
@ -2006,19 +2038,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}, timeout, TimeUnit.MILLISECONDS);
|
}, timeout, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
addAsyncStanzaListener(stanzaListener, replyFilter);
|
addAsyncStanzaListener(stanzaListener, replyFilter);
|
||||||
Runnable sendOperation = () -> {
|
|
||||||
try {
|
try {
|
||||||
sendStanza(stanza);
|
sendStanzaNonBlocking(stanza);
|
||||||
}
|
}
|
||||||
catch (NotConnectedException | InterruptedException exception) {
|
catch (NotConnectedException | OutgoingQueueFullException exception) {
|
||||||
future.setException(exception);
|
future.setException(exception);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if (SmackConfiguration.TRUELY_ASYNC_SENDS) {
|
|
||||||
Async.go(sendOperation);
|
|
||||||
} else {
|
|
||||||
sendOperation.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
|
@ -387,13 +387,4 @@ public final class SmackConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If enabled, causes {@link AbstractXMPPConnection} to create a thread for every asynchronous send operation. This
|
|
||||||
* is meant to work-around a shortcoming of Smack 4.4, where certain send operations are not asynchronous even if
|
|
||||||
* they should be. This is an expert setting, do not toggle if you do not understand the consequences or have been
|
|
||||||
* told to do so. Note that it is expected that this will not be needed in future Smack versions.
|
|
||||||
*
|
|
||||||
* @since 4.4.6
|
|
||||||
*/
|
|
||||||
public static boolean TRUELY_ASYNC_SENDS = false;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,12 @@ public abstract class SmackException extends Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class OutgoingQueueFullException extends SmackException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class IllegalStateChangeException extends SmackException {
|
public static class IllegalStateChangeException extends SmackException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
|
@ -44,35 +46,34 @@ import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.EntityFullJid;
|
import org.jxmpp.jid.EntityFullJid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The XMPPConnection interface provides an interface for connections to an XMPP server and
|
* The XMPPConnection interface provides an interface for connections from a client to an XMPP server and
|
||||||
* implements shared methods which are used by the different types of connections (e.g.
|
* implements shared methods which are used by the different types of connections (e.g.
|
||||||
* <code>XMPPTCPConnection</code> or <code>XMPPBOSHConnection</code>). To create a connection to an XMPP server
|
* {@link org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection} or <code>XMPPTCPConnection</code>). To create a connection to an XMPP server
|
||||||
* a simple usage of this API might look like the following:
|
* a simple usage of this API might look like the following:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>{@code
|
||||||
* // Create a connection to the igniterealtime.org XMPP server.
|
* // Create the configuration for this new connection
|
||||||
* XMPPTCPConnection con = new XMPPTCPConnection("igniterealtime.org");
|
* XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.builder();
|
||||||
* // Connect to the server
|
* configBuilder.setUsernameAndPassword("username", "password");
|
||||||
* con.connect();
|
* configBuilder.setXmppDomain("jabber.org");
|
||||||
* // Most servers require you to login before performing other tasks.
|
*
|
||||||
* con.login("jsmith", "mypass");
|
* AbstractXMPPConnection connection = new XMPPTCPConnection(configBuilder.build());
|
||||||
* // Start a new conversation with John Doe and send him a message.
|
* connection.connect();
|
||||||
* ChatManager chatManager = ChatManager.getInstanceFor(con);
|
* connection.login();
|
||||||
* chatManager.addIncomingListener(new IncomingChatMessageListener() {
|
*
|
||||||
* public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
* Message message = connection.getStanzaFactory().buildMessageStanza()
|
||||||
* // Print out any messages we get back to standard out.
|
* .to("mark@example.org)
|
||||||
* System.out.println("Received message: " + message);
|
* .setBody("Hi, how are you?")
|
||||||
* }
|
* .build();
|
||||||
* });
|
* connection.sendStanza(message);
|
||||||
* Chat chat = chatManager.chatWith("jdoe@igniterealtime.org");
|
*
|
||||||
* chat.send("Howdy!");
|
* connection.disconnect();
|
||||||
* // Disconnect from the server
|
* }</pre>
|
||||||
* con.disconnect();
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
* <p>
|
||||||
* Note that the XMPPConnection interface does intentionally not declare any methods that manipulate
|
* Note that the XMPPConnection interface does intentionally not declare any methods that manipulate
|
||||||
* the connection state, e.g. <code>connect()</code>, <code>disconnect()</code>. You should use the
|
* the connection state, e.g. <code>connect()</code>, <code>disconnect()</code>. You should use the
|
||||||
* most specific connection type, e.g. <code>XMPPTCPConnection</code> as declared type and use the
|
* most-generic superclass connection type that is able to provide the methods you require. In most cases
|
||||||
|
* this should be {@link AbstractXMPPConnection}. And use or hand out instances of the
|
||||||
* XMPPConnection interface when you don't need to manipulate the connection state.
|
* XMPPConnection interface when you don't need to manipulate the connection state.
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -80,6 +81,13 @@ import org.jxmpp.jid.EntityFullJid;
|
||||||
* disconnected and then connected again. Listeners of the XMPPConnection will be retained across
|
* disconnected and then connected again. Listeners of the XMPPConnection will be retained across
|
||||||
* connections.
|
* connections.
|
||||||
* </p>
|
* </p>
|
||||||
|
* <h2>Processing Incoming Stanzas</h2>
|
||||||
|
* Smack provides a flexible framework for processing incoming stanzas using two constructs:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link StanzaCollector}: lets you synchronously wait for new stanzas</li>
|
||||||
|
* <li>{@link StanzaListener}: an interface for asynchronously notifying you of incoming stanzas</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* <h2>Incoming Stanza Listeners</h2>
|
* <h2>Incoming Stanza Listeners</h2>
|
||||||
* Most callbacks (listeners, handlers, …) than you can add to a connection come in three different variants:
|
* Most callbacks (listeners, handlers, …) than you can add to a connection come in three different variants:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -101,9 +109,22 @@ import org.jxmpp.jid.EntityFullJid;
|
||||||
* exact order of how events happen there, most importantly the arrival order of incoming stanzas. You should only
|
* exact order of how events happen there, most importantly the arrival order of incoming stanzas. You should only
|
||||||
* use synchronous callbacks in rare situations.
|
* use synchronous callbacks in rare situations.
|
||||||
* </p>
|
* </p>
|
||||||
|
* <h2>Stanza Filters</h2>
|
||||||
|
* Stanza filters allow you to define the predicates for which listeners or collectors should be invoked. For more
|
||||||
|
* information about stanza filters, see {@link org.jivesoftware.smack.filter}.
|
||||||
|
* <h2>Provider Architecture</h2>
|
||||||
|
* XMPP is an extensible protocol. Smack allows for this extensible with its provider architecture that allows to
|
||||||
|
* plug-in providers that are able to parse the various XML extension elements used for XMPP's extensibility. For
|
||||||
|
* more information see {@link org.jivesoftware.smack.provider}.
|
||||||
|
* <h2>Debugging</h2>
|
||||||
|
* See {@link org.jivesoftware.smack.debugger} for Smack's API to debug XMPP connections.
|
||||||
|
* <h2>Modular Connection Architecture</h2>
|
||||||
|
* Smack's new modular connection architecture will one day replace the monolithic architecture. Its main entry
|
||||||
|
* point {@link org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection} has more information.
|
||||||
*
|
*
|
||||||
* @author Matt Tucker
|
* @author Matt Tucker
|
||||||
* @author Guenther Niess
|
* @author Guenther Niess
|
||||||
|
* @author Florian Schmaus
|
||||||
*/
|
*/
|
||||||
public interface XMPPConnection {
|
public interface XMPPConnection {
|
||||||
|
|
||||||
|
@ -140,6 +161,14 @@ public interface XMPPConnection {
|
||||||
*/
|
*/
|
||||||
EntityFullJid getUser();
|
EntityFullJid getUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local address currently in use for this connection, or <code>null</code> if
|
||||||
|
* this is invalid for the type of underlying connection.
|
||||||
|
*
|
||||||
|
* @return the local address currently in use for this connection
|
||||||
|
*/
|
||||||
|
InetAddress getLocalAddress();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stream ID for this connection, which is the value set by the server
|
* Returns the stream ID for this connection, which is the value set by the server
|
||||||
* when opening an XMPP stream. This value will be <code>null</code> if not connected to the server.
|
* when opening an XMPP stream. This value will be <code>null</code> if not connected to the server.
|
||||||
|
@ -199,6 +228,8 @@ public interface XMPPConnection {
|
||||||
* */
|
* */
|
||||||
void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
|
void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
void sendStanzaNonBlocking(Stanza stanza) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to send the given stanza. Returns {@code true} if the stanza was successfully put into the outgoing stanza
|
* Try to send the given stanza. Returns {@code true} if the stanza was successfully put into the outgoing stanza
|
||||||
* queue, otherwise, if {@code false} is returned, the stanza could not be scheduled for sending (for example
|
* queue, otherwise, if {@code false} is returned, the stanza could not be scheduled for sending (for example
|
||||||
|
@ -213,7 +244,10 @@ public interface XMPPConnection {
|
||||||
* @return {@code true} if the stanza was successfully scheduled to be send, {@code false} otherwise.
|
* @return {@code true} if the stanza was successfully scheduled to be send, {@code false} otherwise.
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
* @throws NotConnectedException if the connection is not connected.
|
||||||
* @since 4.4.0
|
* @since 4.4.0
|
||||||
|
* @deprecated use {@link #sendStanzaNonBlocking(Stanza)} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.7.
|
||||||
|
@Deprecated
|
||||||
boolean trySendStanza(Stanza stanza) throws NotConnectedException;
|
boolean trySendStanza(Stanza stanza) throws NotConnectedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -234,7 +268,10 @@ public interface XMPPConnection {
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
* @throws NotConnectedException if the connection is not connected.
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
* @since 4.4.0
|
* @since 4.4.0
|
||||||
|
* @deprecated use {@link #sendStanzaNonBlocking(Stanza)} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.7.
|
||||||
|
@Deprecated
|
||||||
boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit) throws NotConnectedException, InterruptedException;
|
boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -251,6 +288,8 @@ public interface XMPPConnection {
|
||||||
*/
|
*/
|
||||||
void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException;
|
void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
void sendNonzaNonBlocking(Nonza stanza) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a connection listener to this connection that will be notified when
|
* Adds a connection listener to this connection that will be notified when
|
||||||
* the connection closes or fails.
|
* the connection closes or fails.
|
||||||
|
@ -350,7 +389,7 @@ public interface XMPPConnection {
|
||||||
/**
|
/**
|
||||||
* Registers a stanza listener with this connection. The listener will be invoked when a (matching) incoming stanza
|
* Registers a stanza listener with this connection. The listener will be invoked when a (matching) incoming stanza
|
||||||
* is received. The stanza filter determines which stanzas will be delivered to the listener. It is guaranteed that
|
* is received. The stanza filter determines which stanzas will be delivered to the listener. It is guaranteed that
|
||||||
* the same listener will not be invoked concurrently and the the order of invocation will reflect the order in
|
* the same listener will not be invoked concurrently and the order of invocation will reflect the order in
|
||||||
* which the stanzas have been received. If the same stanza listener is added again with a different filter, only
|
* which the stanzas have been received. If the same stanza listener is added again with a different filter, only
|
||||||
* the new filter will be used.
|
* the new filter will be used.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2021 Florian Schmaus
|
* Copyright 2018-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
package org.jivesoftware.smack.c2s;
|
package org.jivesoftware.smack.c2s;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -37,6 +38,7 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
||||||
|
@ -67,7 +69,6 @@ import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
@ -86,9 +87,55 @@ import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
import org.jxmpp.util.XmppStringUtils;
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The superclass of Smack's Modular Connection Architecture.
|
||||||
|
* <p>
|
||||||
|
* <b>Note:</b> Everything related to the modular connection architecture is currently considered experimental and
|
||||||
|
* should not be used in production. Use the mature {@code XMPPTCPConnection} if you do not feel adventurous.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Smack's modular connection architecture allows to extend a XMPP c2s (client-to-server) connection with additional
|
||||||
|
* functionality by adding modules. Those modules extend the Finite State Machine (FSM) within the connection with new
|
||||||
|
* states. Connection modules can either be
|
||||||
|
* <ul>
|
||||||
|
* <li>Transports</li>
|
||||||
|
* <li>Extensions</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Transports bind the XMPP XML stream to an underlying transport like TCP, WebSockets, BOSH, and allow for the
|
||||||
|
* different particularities of transports like DirectTLS
|
||||||
|
* (<a href="https://xmpp.org/extensions/xep-0368.html">XEP-0368</a>). 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.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Extensions allow for a richer functionality of the connection. Those include
|
||||||
|
* <ul>
|
||||||
|
* <li>Compression</li>
|
||||||
|
* <li><ul>
|
||||||
|
* <li>zlib ([XEP-0138](https://xmpp.org/extensions/xep-0138.html))</li>
|
||||||
|
* <li>[Efficient XML Interchange (EXI)](https://www.w3.org/TR/exi/)</li>
|
||||||
|
* </ul></li>
|
||||||
|
* <li>Instant Stream Resumption ([XEP-0397](https://xmpp.org/extensions/xep-0397.html)</li>
|
||||||
|
* <li>Bind2</li>
|
||||||
|
* <li>Stream Management</li>
|
||||||
|
* </ul>
|
||||||
|
* Note that not all extensions work with every transport. For example compression only works with TCP-based transport
|
||||||
|
* bindings.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 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 and states are
|
||||||
|
* Instantiated once a modular connection is instantiated.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {
|
public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ModularXmppClientToServerConnectionConfiguration.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(
|
||||||
|
ModularXmppClientToServerConnectionConfiguration.class.getName());
|
||||||
|
|
||||||
private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
|
private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
|
||||||
100, true);
|
100, true);
|
||||||
|
@ -126,7 +173,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
|
||||||
// Construct the internal connection API.
|
// Construct the internal connection API.
|
||||||
connectionInternal = new ModularXmppClientToServerConnectionInternal(this, getReactor(), debugger, outgoingElementsQueue) {
|
connectionInternal = new ModularXmppClientToServerConnectionInternal(this, getReactor(), debugger,
|
||||||
|
outgoingElementsQueue) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parseAndProcessElement(String wrappedCompleteElement) {
|
public void parseAndProcessElement(String wrappedCompleteElement) {
|
||||||
|
@ -180,13 +228,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
|
public void waitForFeaturesReceived(String waitFor)
|
||||||
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
|
ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
public void newStreamOpenWaitForFeaturesSequence(String waitFor)
|
||||||
SmackException, XMPPException {
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
|
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,9 +245,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza, Class<SN> successNonzaClass,
|
public <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza,
|
||||||
Class<FN> failedNonzaClass) throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException {
|
Class<SN> successNonzaClass, Class<FN> failedNonzaClass) throws NoResponseException,
|
||||||
return ModularXmppClientToServerConnection.this.sendAndWaitForResponse(nonza, successNonzaClass, failedNonzaClass);
|
NotConnectedException, FailedNonzaException, InterruptedException {
|
||||||
|
return ModularXmppClientToServerConnection.this.sendAndWaitForResponse(nonza, successNonzaClass,
|
||||||
|
failedNonzaClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -234,7 +285,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
// modules are sometimes used to construct the states.
|
// modules are sometimes used to construct the states.
|
||||||
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : configuration.moduleDescriptors) {
|
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : configuration.moduleDescriptors) {
|
||||||
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
|
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
|
||||||
ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> connectionModule = moduleDescriptor.constructXmppConnectionModule(connectionInternal);
|
ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> connectionModule = moduleDescriptor.constructXmppConnectionModule(
|
||||||
|
connectionInternal);
|
||||||
connectionModules.put(moduleDescriptorClass, connectionModule);
|
connectionModules.put(moduleDescriptorClass, connectionModule);
|
||||||
|
|
||||||
XmppClientToServerTransport transport = connectionModule.getTransport();
|
XmppClientToServerTransport transport = connectionModule.getTransport();
|
||||||
|
@ -265,7 +317,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
private WalkStateGraphContext.Builder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
|
private WalkStateGraphContext.Builder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
|
||||||
return WalkStateGraphContext.builder(currentStateVertex.getElement().getStateDescriptor().getClass(), finalStateClass);
|
return WalkStateGraphContext.builder(currentStateVertex.getElement().getStateDescriptor().getClass(),
|
||||||
|
finalStateClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -316,7 +369,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
List<GraphVertex<State>> outgoingStateEdges = initialStateVertex.getOutgoingEdges();
|
List<GraphVertex<State>> outgoingStateEdges = initialStateVertex.getOutgoingEdges();
|
||||||
|
|
||||||
// See if we need to handle mandatory intermediate states.
|
// See if we need to handle mandatory intermediate states.
|
||||||
GraphVertex<State> mandatoryIntermediateStateVertex = walkStateGraphContext.maybeReturnMandatoryImmediateState(outgoingStateEdges);
|
GraphVertex<State> mandatoryIntermediateStateVertex = walkStateGraphContext.maybeReturnMandatoryImmediateState(
|
||||||
|
outgoingStateEdges);
|
||||||
if (mandatoryIntermediateStateVertex != null) {
|
if (mandatoryIntermediateStateVertex != null) {
|
||||||
StateTransitionResult reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
|
StateTransitionResult reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
|
||||||
|
|
||||||
|
@ -340,7 +394,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
// state.
|
// state.
|
||||||
if (walkStateGraphContext.wouldCauseCycle(successorStateVertex)) {
|
if (walkStateGraphContext.wouldCauseCycle(successorStateVertex)) {
|
||||||
// Ignore this successor.
|
// Ignore this successor.
|
||||||
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionIgnoredDueCycle(initialStateVertex, successorStateVertex));
|
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionIgnoredDueCycle(
|
||||||
|
initialStateVertex, successorStateVertex));
|
||||||
} else {
|
} else {
|
||||||
StateTransitionResult result = attemptEnterState(successorStateVertex, walkStateGraphContext);
|
StateTransitionResult result = attemptEnterState(successorStateVertex, walkStateGraphContext);
|
||||||
|
|
||||||
|
@ -348,9 +403,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then we
|
// If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then
|
||||||
|
// we
|
||||||
// just record this value and go on from there. Note that reason may be null, which is returned by
|
// just record this value and go on from there. Note that reason may be null, which is returned by
|
||||||
// attemptEnterState in case the state was already successfully handled. If this is the case, then we don't
|
// attemptEnterState in case the state was already successfully handled. If this is the case, then we
|
||||||
|
// don't
|
||||||
// record it.
|
// record it.
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
walkStateGraphContext.recordFailedState(successorState, result);
|
walkStateGraphContext.recordFailedState(successorState, result);
|
||||||
|
@ -379,8 +436,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
*/
|
*/
|
||||||
private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
|
private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
|
||||||
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPException,
|
WalkStateGraphContext walkStateGraphContext)
|
||||||
IOException, InterruptedException {
|
throws SmackException, XMPPException, IOException, InterruptedException {
|
||||||
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
||||||
final State initialState = initialStateVertex.getElement();
|
final State initialState = initialStateVertex.getElement();
|
||||||
final State successorState = successorStateVertex.getElement();
|
final State successorState = successorStateVertex.getElement();
|
||||||
|
@ -396,8 +453,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
if (successorStateDescriptor.isNotImplemented()) {
|
if (successorStateDescriptor.isNotImplemented()) {
|
||||||
StateTransitionResult.TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(
|
StateTransitionResult.TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(
|
||||||
successorStateDescriptor);
|
successorStateDescriptor);
|
||||||
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState, successorState,
|
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
|
||||||
transtionImpossibleBecauseNotImplemented));
|
successorState, transtionImpossibleBecauseNotImplemented));
|
||||||
return transtionImpossibleBecauseNotImplemented;
|
return transtionImpossibleBecauseNotImplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,12 +463,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
StateTransitionResult.TransitionImpossible transitionImpossible = successorState.isTransitionToPossible(
|
StateTransitionResult.TransitionImpossible transitionImpossible = successorState.isTransitionToPossible(
|
||||||
walkStateGraphContext);
|
walkStateGraphContext);
|
||||||
if (transitionImpossible != null) {
|
if (transitionImpossible != null) {
|
||||||
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState, successorState,
|
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
|
||||||
transitionImpossible));
|
successorState, transitionImpossible));
|
||||||
return transitionImpossible;
|
return transitionImpossible;
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
|
invokeConnectionStateMachineListener(
|
||||||
|
new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
|
||||||
transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
|
transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
|
||||||
} catch (SmackException | IOException | InterruptedException | XMPPException e) {
|
} catch (SmackException | IOException | InterruptedException | XMPPException e) {
|
||||||
// Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
|
// Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
|
||||||
|
@ -421,8 +479,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
if (transitionAttemptResult instanceof StateTransitionResult.Failure) {
|
if (transitionAttemptResult instanceof StateTransitionResult.Failure) {
|
||||||
StateTransitionResult.Failure transitionFailureResult = (StateTransitionResult.Failure) transitionAttemptResult;
|
StateTransitionResult.Failure transitionFailureResult = (StateTransitionResult.Failure) transitionAttemptResult;
|
||||||
invokeConnectionStateMachineListener(
|
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionFailed(initialState, successorState,
|
||||||
new ConnectionStateEvent.TransitionFailed(initialState, successorState, transitionFailureResult));
|
transitionFailureResult));
|
||||||
return transitionAttemptResult;
|
return transitionAttemptResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,16 +496,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza stanza) throws NotConnectedException, InterruptedException {
|
protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
||||||
sendTopLevelStreamElement(stanza);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
|
|
||||||
sendTopLevelStreamElement(nonza);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTopLevelStreamElement(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
|
||||||
final XmppClientToServerTransport transport = activeTransport;
|
final XmppClientToServerTransport transport = activeTransport;
|
||||||
if (transport == null) {
|
if (transport == null) {
|
||||||
throw new NotConnectedException();
|
throw new NotConnectedException();
|
||||||
|
@ -457,6 +506,21 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
transport.notifyAboutNewOutgoingElements();
|
transport.notifyAboutNewOutgoingElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
final XmppClientToServerTransport transport = activeTransport;
|
||||||
|
if (transport == null) {
|
||||||
|
throw new NotConnectedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enqueued = outgoingElementsQueue.offer(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.notifyAboutNewOutgoingElements();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
shutdown(false);
|
shutdown(false);
|
||||||
|
@ -560,8 +624,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
featuresReceived = false;
|
featuresReceived = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForFeaturesReceived(String waitFor)
|
private void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
|
||||||
throws InterruptedException, SmackException, XMPPException {
|
|
||||||
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
|
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,8 +634,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
|
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
private void newStreamOpenWaitForFeaturesSequence(String waitFor)
|
||||||
SmackException, XMPPException {
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
prepareToWaitForFeaturesReceived();
|
prepareToWaitForFeaturesReceived();
|
||||||
|
|
||||||
// Create StreamOpen from StreamOpenAndCloseFactory via underlying transport.
|
// Create StreamOpen from StreamOpenAndCloseFactory via underlying transport.
|
||||||
|
@ -583,7 +646,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
if (localpart != null) {
|
if (localpart != null) {
|
||||||
from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain);
|
from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain);
|
||||||
}
|
}
|
||||||
AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from, getStreamId(), getConfiguration().getXmlLang());
|
AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from,
|
||||||
|
getStreamId(), getConfiguration().getXmlLang());
|
||||||
sendStreamOpen(streamOpen);
|
sendStreamOpen(streamOpen);
|
||||||
|
|
||||||
waitForFeaturesReceived(waitFor);
|
waitForFeaturesReceived(waitFor);
|
||||||
|
@ -603,7 +667,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
private final class DisconnectedState extends State {
|
private final class DisconnectedState extends State {
|
||||||
|
|
||||||
private DisconnectedState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private DisconnectedState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,7 +707,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
private final class LookupRemoteConnectionEndpointsState extends State {
|
private final class LookupRemoteConnectionEndpointsState extends State {
|
||||||
boolean outgoingElementsQueueWasShutdown;
|
boolean outgoingElementsQueueWasShutdown;
|
||||||
|
|
||||||
private LookupRemoteConnectionEndpointsState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private LookupRemoteConnectionEndpointsState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,7 +817,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ConnectedButUnauthenticatedState extends State {
|
private final class ConnectedButUnauthenticatedState extends State {
|
||||||
private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,7 +849,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class SaslAuthenticationState extends State {
|
private final class SaslAuthenticationState extends State {
|
||||||
private SaslAuthenticationState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private SaslAuthenticationState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,14 +899,16 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ResourceBindingState extends State {
|
private final class ResourceBindingState extends State {
|
||||||
private ResourceBindingState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private ResourceBindingState(StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws IOException, SmackException, InterruptedException, XMPPException {
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||||
// Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be signaled.
|
// Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be
|
||||||
|
// signaled.
|
||||||
// Since we entered this state, the FSM has decided that the last features have been received, hence signal
|
// Since we entered this state, the FSM has decided that the last features have been received, hence signal
|
||||||
// the sync point.
|
// the sync point.
|
||||||
lastFeaturesReceived = true;
|
lastFeaturesReceived = true;
|
||||||
|
@ -895,13 +965,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
if (walkFromDisconnectToAuthenticated != null) {
|
if (walkFromDisconnectToAuthenticated != null) {
|
||||||
// If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
|
// If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
|
||||||
// walk must not start from the 'Disconnected' state.
|
// walk must not start from the 'Disconnected' state.
|
||||||
assert walkStateGraphContext.getWalk().get(0).getStateDescriptor().getClass()
|
assert walkStateGraphContext.getWalk().get(
|
||||||
!= DisconnectedStateDescriptor.class;
|
0).getStateDescriptor().getClass() != DisconnectedStateDescriptor.class;
|
||||||
// Append the current walk to the previous one.
|
// Append the current walk to the previous one.
|
||||||
walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
|
walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
|
||||||
} else {
|
} else {
|
||||||
walkFromDisconnectToAuthenticated = new ArrayList<>(
|
walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.getWalkLength() + 1);
|
||||||
walkStateGraphContext.getWalkLength() + 1);
|
|
||||||
walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
|
walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
|
||||||
}
|
}
|
||||||
walkFromDisconnectToAuthenticated.add(this);
|
walkFromDisconnectToAuthenticated.add(this);
|
||||||
|
@ -931,7 +1000,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
|
public StateTransitionResult.TransitionImpossible isTransitionToPossible(
|
||||||
|
WalkStateGraphContext walkStateGraphContext) {
|
||||||
ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
|
ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -962,7 +1032,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
XmppInputOutputFilter filter = it.next();
|
XmppInputOutputFilter filter = it.next();
|
||||||
try {
|
try {
|
||||||
filter.waitUntilInputOutputClosed();
|
filter.waitUntilInputOutputClosed();
|
||||||
} catch (IOException | CertificateException | InterruptedException | SmackException | XMPPException e) {
|
} catch (IOException | CertificateException | InterruptedException | SmackException
|
||||||
|
| XMPPException e) {
|
||||||
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
|
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -985,12 +1056,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class InstantShutdownState extends NoOpState {
|
private static final class InstantShutdownState extends NoOpState {
|
||||||
private InstantShutdownState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
private InstantShutdownState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(connection, stateDescriptor, connectionInternal);
|
super(connection, stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
|
public StateTransitionResult.TransitionImpossible isTransitionToPossible(
|
||||||
|
WalkStateGraphContext walkStateGraphContext) {
|
||||||
ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
|
ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1051,11 +1124,16 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
|
protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
|
||||||
WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(ConnectedButUnauthenticatedStateDescriptor.class)
|
WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(
|
||||||
.build();
|
ConnectedButUnauthenticatedStateDescriptor.class).build();
|
||||||
walkStateGraph(walkStateGraphContext);
|
walkStateGraph(walkStateGraphContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> getFilterStats() {
|
private Map<String, Object> getFilterStats() {
|
||||||
Collection<XmppInputOutputFilter> filters;
|
Collection<XmppInputOutputFilter> filters;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2002-2008 Jive Software, 2015-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core debugger functionality.
|
* Smack includes built-in debugging consoles that will let you track all XML traffic between the client and server.
|
||||||
|
* Further debuggers, besides those provide by smack-core, can be found in org.jivesoftware.smackx.debugger (note, that
|
||||||
|
* this uses the smackx namespace, and not smack) provided by smack-debug.
|
||||||
|
*
|
||||||
|
* Debugging mode can be enabled in two different ways.
|
||||||
|
*
|
||||||
|
* Add the following line of code before creating new connections
|
||||||
|
*
|
||||||
|
* {@code SmackConfiguration.DEBUG = true;}
|
||||||
|
*
|
||||||
|
* Set the Java system property smack.debugEnabled to {@code true}. The system property can be set on the command line such as
|
||||||
|
*
|
||||||
|
* <pre>java -Dsmack.debugEnabled=true SomeApp</pre>
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.debugger;
|
package org.jivesoftware.smack.debugger;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020-2021 Florian Schmaus
|
* Copyright 2020-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -28,7 +28,7 @@ public class ExtensionElementFilter<E extends ExtensionElement> implements Stanz
|
||||||
private final Class<E> extensionElementClass;
|
private final Class<E> extensionElementClass;
|
||||||
private final QName extensionElementQName;
|
private final QName extensionElementQName;
|
||||||
|
|
||||||
protected ExtensionElementFilter(Class<E> extensionElementClass) {
|
public ExtensionElementFilter(Class<E> extensionElementClass) {
|
||||||
this.extensionElementClass = extensionElementClass;
|
this.extensionElementClass = extensionElementClass;
|
||||||
extensionElementQName = XmppElementUtil.getQNameFor(extensionElementClass);
|
extensionElementQName = XmppElementUtil.getQNameFor(extensionElementClass);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,5 +17,16 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows {@link org.jivesoftware.smack.StanzaCollector} and {@link org.jivesoftware.smack.StanzaListener} instances to filter for stanzas with particular attributes.
|
* Allows {@link org.jivesoftware.smack.StanzaCollector} and {@link org.jivesoftware.smack.StanzaListener} instances to filter for stanzas with particular attributes.
|
||||||
|
* <h2>Selected Filter Types</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link StanzaTypeFilter}: filters for stanzas that are a stanza type (Message, Presence, or IQ)</li>
|
||||||
|
* <li>{@link StanzaIdFilter}: filters for stanzas with a particular stanza ID</li>
|
||||||
|
* <li>{@link ToMatchesFilter}: filters for stanzas that are sent to a particular address</li>
|
||||||
|
* <li>{@link FromMatchesFilter}: filters for stanzas that are sent from a particular address</li>
|
||||||
|
* <li>{@link ExtensionElementFilter}: filters for stanzas that have a particular stanza exentsion element</li>
|
||||||
|
* <li>{@link AndFilter}: implements the logical AND operation over two or more filters</li>
|
||||||
|
* <li>{@link OrFilter}: implements the logical OR operation over two or more filters</li>
|
||||||
|
* <li>{@link NotFilter}: implements the logical NOT operation on a filter</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.filter;
|
package org.jivesoftware.smack.filter;
|
||||||
|
|
|
@ -51,7 +51,7 @@ public abstract class AbstractIqBuilder<IB extends AbstractIqBuilder<IB>> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static IqData createResponse(IqView request, IQ.ResponseType responseType) {
|
protected static IqData createResponse(IqView request, IQ.ResponseType responseType) {
|
||||||
if (!(request.getType() == IQ.Type.get || request.getType() == IQ.Type.set)) {
|
if (!request.isRequestIQ()) {
|
||||||
throw new IllegalArgumentException("IQ request must be of type 'set' or 'get'. Original IQ: " + request);
|
throw new IllegalArgumentException("IQ request must be of type 'set' or 'get'. Original IQ: " + request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2019 Florian Schmaus
|
* Copyright © 2014-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,7 +24,7 @@ public class EmptyResultIQ extends IQ {
|
||||||
|
|
||||||
// TODO: Deprecate when stanza builder and parsing logic is ready.
|
// TODO: Deprecate when stanza builder and parsing logic is ready.
|
||||||
public EmptyResultIQ() {
|
public EmptyResultIQ() {
|
||||||
super(null, null);
|
super((String) null, null);
|
||||||
setType(IQ.Type.result);
|
setType(IQ.Type.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014 Florian Schmaus
|
* Copyright © 2014-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,24 +16,82 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ErrorIQ extends SimpleIQ {
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An XMPP error IQ.
|
||||||
|
* <p>
|
||||||
|
* According to RFC 6120 § 8.3.1 "4. An error stanza MUST contain an <error/> child element.", so this class can
|
||||||
|
* only be constructed if a stanza error is provided.
|
||||||
|
*/
|
||||||
|
public final class ErrorIQ extends IQ {
|
||||||
|
|
||||||
public static final String ELEMENT = StanzaError.ERROR;
|
public static final String ELEMENT = StanzaError.ERROR;
|
||||||
|
|
||||||
/**
|
private final IQ request;
|
||||||
* Constructs a new error IQ.
|
|
||||||
* <p>
|
private ErrorIQ(Builder builder, QName childElementQName) {
|
||||||
* According to RFC 6120 § 8.3.1 "4. An error stanza MUST contain an <error/> child element.", so the xmppError argument is mandatory.
|
super(builder, childElementQName);
|
||||||
* </p>
|
Objects.requireNonNull(builder.getError(), "Must provide an stanza error when building error IQs");
|
||||||
* @param stanzaError the stanzaError (required).
|
this.request = builder.request;
|
||||||
*/
|
}
|
||||||
public ErrorIQ(StanzaError stanzaError) {
|
|
||||||
super(ELEMENT, null);
|
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
|
||||||
Objects.requireNonNull(stanzaError, "stanzaError must not be null");
|
Builder builder = new Builder(error, request);
|
||||||
setType(IQ.Type.error);
|
builder.setError(error);
|
||||||
setError(stanzaError);
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||||
|
if (request == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.getIQChildElementBuilder(xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(StanzaError error) {
|
||||||
|
return new Builder(error, IqData.EMPTY.ofType(IQ.Type.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(StanzaError error, IqData iqData) {
|
||||||
|
return new Builder(error, iqData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends IqBuilder<Builder, ErrorIQ> {
|
||||||
|
|
||||||
|
private IQ request;
|
||||||
|
|
||||||
|
Builder(StanzaError error, IqData iqData) {
|
||||||
|
super(iqData);
|
||||||
|
if (iqData.getType() != IQ.Type.error) {
|
||||||
|
throw new IllegalArgumentException("Error IQs must be of type 'error'");
|
||||||
|
}
|
||||||
|
Objects.requireNonNull(error, "Must provide an stanza error when building error IQs");
|
||||||
|
setError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder(StanzaError error, IQ request) {
|
||||||
|
this(error, AbstractIqBuilder.createErrorResponse(request));
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorIQ build() {
|
||||||
|
QName childElementQname = null;
|
||||||
|
if (request != null) {
|
||||||
|
childElementQname = request.getChildElementQName();
|
||||||
|
}
|
||||||
|
return new ErrorIQ(this, childElementQname);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -68,16 +68,22 @@ public abstract class IQ extends Stanza implements IqView {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IQ(AbstractIqBuilder<?> iqBuilder, String childElementName, String childElementNamespace) {
|
protected IQ(AbstractIqBuilder<?> iqBuilder, String childElementName, String childElementNamespace) {
|
||||||
|
this(iqBuilder, childElementName != null ? new QName(childElementNamespace, childElementName) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IQ(AbstractIqBuilder<?> iqBuilder, QName childElementQName) {
|
||||||
super(iqBuilder);
|
super(iqBuilder);
|
||||||
|
|
||||||
type = iqBuilder.type;
|
type = iqBuilder.type;
|
||||||
|
|
||||||
this.childElementName = childElementName;
|
if (childElementQName != null) {
|
||||||
this.childElementNamespace = childElementNamespace;
|
this.childElementQName = childElementQName;
|
||||||
if (childElementName == null) {
|
this.childElementName = childElementQName.getLocalPart();
|
||||||
childElementQName = null;
|
this.childElementNamespace = childElementQName.getNamespaceURI();
|
||||||
} else {
|
} else {
|
||||||
childElementQName = new QName(childElementNamespace, childElementName);
|
this.childElementQName = null;
|
||||||
|
this.childElementName = null;
|
||||||
|
this.childElementNamespace = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,32 +106,6 @@ public abstract class IQ extends Stanza implements IqView {
|
||||||
this.type = Objects.requireNonNull(type, "type must not be null");
|
this.type = Objects.requireNonNull(type, "type must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if this IQ is a request IQ, i.e. an IQ of type {@link Type#get} or {@link Type#set}.
|
|
||||||
*
|
|
||||||
* @return true if IQ type is 'get' or 'set', false otherwise.
|
|
||||||
* @since 4.1
|
|
||||||
*/
|
|
||||||
public boolean isRequestIQ() {
|
|
||||||
switch (type) {
|
|
||||||
case get:
|
|
||||||
case set:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if this IQ is a request, i.e. an IQ of type {@link Type#result} or {@link Type#error}.
|
|
||||||
*
|
|
||||||
* @return true if IQ type is 'result' or 'error', false otherwise.
|
|
||||||
* @since 4.4
|
|
||||||
*/
|
|
||||||
public boolean isResponseIQ() {
|
|
||||||
return !isRequestIQ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final QName getChildElementQName() {
|
public final QName getChildElementQName() {
|
||||||
return childElementQName;
|
return childElementQName;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +174,6 @@ public abstract class IQ extends Stanza implements IqView {
|
||||||
if (type == Type.error) {
|
if (type == Type.error) {
|
||||||
// Add the error sub-packet, if there is one.
|
// Add the error sub-packet, if there is one.
|
||||||
appendErrorIfExists(xml);
|
appendErrorIfExists(xml);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (childElementName == null) {
|
if (childElementName == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -305,16 +284,7 @@ public abstract class IQ extends Stanza implements IqView {
|
||||||
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
|
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
|
||||||
*/
|
*/
|
||||||
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
|
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
|
||||||
if (!request.isRequestIQ()) {
|
return ErrorIQ.createErrorResponse(request, error);
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
|
|
||||||
}
|
|
||||||
final ErrorIQ result = new ErrorIQ(error);
|
|
||||||
result.setStanzaId(request.getStanzaId());
|
|
||||||
result.setFrom(request.getTo());
|
|
||||||
result.setTo(request.getFrom());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019 Florian Schmaus
|
* Copyright 2019-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.IQ.Type;
|
||||||
|
|
||||||
public interface IqView extends StanzaView {
|
public interface IqView extends StanzaView {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,4 +27,24 @@ public interface IqView extends StanzaView {
|
||||||
*/
|
*/
|
||||||
IQ.Type getType();
|
IQ.Type getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this IQ is a request IQ, i.e. an IQ of type {@link Type#get} or {@link Type#set}.
|
||||||
|
*
|
||||||
|
* @return true if IQ type is 'get' or 'set', false otherwise.
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
default boolean isRequestIQ() {
|
||||||
|
IQ.Type type = getType();
|
||||||
|
return type == IQ.Type.get || type == IQ.Type.set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this IQ is a request, i.e. an IQ of type {@link Type#result} or {@link Type#error}.
|
||||||
|
*
|
||||||
|
* @return true if IQ type is 'result' or 'error', false otherwise.
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
default boolean isResponseIQ() {
|
||||||
|
return !isRequestIQ();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements Stanz
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets who the the stanza is being sent "from".
|
* Sets who the stanza is being sent "from".
|
||||||
*
|
*
|
||||||
* @param from who the stanza is being sent from.
|
* @param from who the stanza is being sent from.
|
||||||
* @return a reference to this builder.
|
* @return a reference to this builder.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019-2021 Florian Schmaus
|
* Copyright 2019-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -31,6 +31,62 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
* An abstract class for parsing custom {@link IQ} packets. Each IqProvider must be registered with the {@link
|
* An abstract class for parsing custom {@link IQ} packets. Each IqProvider must be registered with the {@link
|
||||||
* ProviderManager} for it to be used. Every implementation of this abstract class <b>must</b> have a public,
|
* ProviderManager} for it to be used. Every implementation of this abstract class <b>must</b> have a public,
|
||||||
* no-argument constructor.
|
* no-argument constructor.
|
||||||
|
* <h2>Custom IQ Provider Example</h2>
|
||||||
|
* <p>
|
||||||
|
* Let us assume you want to write a provider for a new, unsupported IQ in Smack.
|
||||||
|
* </p>
|
||||||
|
* <pre>{@code
|
||||||
|
* <iq type='set' from='juliet@capulet.example/balcony' to='romeo@montage.example'>
|
||||||
|
* <myiq xmlns='example:iq:foo' token='secret'>
|
||||||
|
* <user age='42'>John Doe</user>
|
||||||
|
* <location>New York</location>
|
||||||
|
* </myiq>
|
||||||
|
* </iq>
|
||||||
|
* }</pre>
|
||||||
|
* The custom IQ provider may look like the follows
|
||||||
|
* <pre>{@code
|
||||||
|
* public class MyIQProvider extends IQProvider<MyIQ> {
|
||||||
|
*
|
||||||
|
* {@literal @}Override
|
||||||
|
* public MyIQ parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException {
|
||||||
|
* // Define the data we are trying to collect with sane defaults
|
||||||
|
* int age = -1;
|
||||||
|
* String user = null;
|
||||||
|
* String location = null;
|
||||||
|
*
|
||||||
|
* // Start parsing loop
|
||||||
|
* outerloop: while(true) {
|
||||||
|
* XmlPullParser.Event eventType = parser.next();
|
||||||
|
* switch(eventType) {
|
||||||
|
* case START_ELEMENT:
|
||||||
|
* String elementName = parser.getName();
|
||||||
|
* switch (elementName) {
|
||||||
|
* case "user":
|
||||||
|
* age = ParserUtils.getIntegerAttribute(parser, "age");
|
||||||
|
* user = parser.nextText();
|
||||||
|
* break;
|
||||||
|
* case "location"
|
||||||
|
* location = parser.nextText();
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* break;
|
||||||
|
* case END_ELEMENT:
|
||||||
|
* // Abort condition: if the are on a end tag (closing element) of the same depth
|
||||||
|
* if (parser.getDepth() == initialDepth) {
|
||||||
|
* break outerloop;
|
||||||
|
* }
|
||||||
|
* break;
|
||||||
|
* default:
|
||||||
|
* // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Construct the IQ instance at the end of parsing, when all data has been collected
|
||||||
|
* return new MyIQ(user, age, location);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @param <I> the {@link IQ} that is parsed by implementations.
|
* @param <I> the {@link IQ} that is parsed by implementations.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,29 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides pluggable parsing of incoming IQ's and extensions elements.
|
* The Smack provider architecture is a system for plugging in custom XML parsing of staza extensions
|
||||||
|
* ({@link org.jivesoftware.smack.packet.ExtensionElement}, {@link org.jivesoftware.smack.packet.IQ} stanzas and
|
||||||
|
* {@link org.jivesoftware.smack.packet.Nonza}. Hence, there are the the following providers:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link ExtensionElementProvider}</li>
|
||||||
|
* <li>{@link IqProvider}</li>
|
||||||
|
* <li>{@link NonzaProvider}</li>
|
||||||
|
* </ul>
|
||||||
|
* For most users, only extension element and IQ providers should be relevant.
|
||||||
|
* <h2>Architecture</h2>
|
||||||
|
* <p>
|
||||||
|
* Providers are registered with the {@link ProviderManager}. XML elements identified by their
|
||||||
|
* {@link javax.xml.namespace.QName}, that is, their qualified name consistent of the XML elements name and its
|
||||||
|
* namespace. The QName is hence used to map XML elements to their provider Whenever a stanza extension is found in a
|
||||||
|
* stanza, parsing will be passed to the correct provider. Each provider is responsible for parsing the XML stream via
|
||||||
|
* Smack's {@link org.jivesoftware.smack.xml.XmlPullParser}.
|
||||||
|
* </p>
|
||||||
|
* <h2>Unknown Extension Elements</h2>
|
||||||
|
* <p>
|
||||||
|
* If no extension element provider is registered for an element, then Smack will fall back to parse the "unknown"
|
||||||
|
* element to a {@link org.jivesoftware.smack.packet.StandardExtensionElement}.
|
||||||
|
* </p>
|
||||||
|
* <h2>Custom Provider Example</h2>
|
||||||
|
* See {@link IqProvider} for examples.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.provider;
|
package org.jivesoftware.smack.provider;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019 Florian Schmaus
|
* Copyright 2019-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -65,12 +65,32 @@ public abstract class InternetAddress implements CharSequence {
|
||||||
return originalString.subSequence(start, end);
|
return originalString.subSequence(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRaw() {
|
||||||
|
return originalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InternetAddress fromIgnoringZoneId(String address) {
|
||||||
|
return from(address, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static InternetAddress from(String address) {
|
public static InternetAddress from(String address) {
|
||||||
|
return from(address, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InternetAddress from(String address, boolean ignoreZoneId) {
|
||||||
|
String raw = address;
|
||||||
|
if (ignoreZoneId) {
|
||||||
|
int percentPosition = address.indexOf('%');
|
||||||
|
if (percentPosition > 1) {
|
||||||
|
address = address.substring(0, percentPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final InternetAddress internetAddress;
|
final InternetAddress internetAddress;
|
||||||
if (InetAddressUtil.isIpV4Address(address)) {
|
if (InetAddressUtil.isIpV4Address(address)) {
|
||||||
internetAddress = new InternetAddress.Ipv4(address);
|
internetAddress = new InternetAddress.Ipv4(address, raw);
|
||||||
} else if (InetAddressUtil.isIpV6Address(address)) {
|
} else if (InetAddressUtil.isIpV6Address(address)) {
|
||||||
internetAddress = new InternetAddress.Ipv6(address);
|
internetAddress = new InternetAddress.Ipv6(address, raw);
|
||||||
} else if (address.contains(".")) {
|
} else if (address.contains(".")) {
|
||||||
InternetAddress domainNameInternetAddress;
|
InternetAddress domainNameInternetAddress;
|
||||||
try {
|
try {
|
||||||
|
@ -99,9 +119,11 @@ public abstract class InternetAddress implements CharSequence {
|
||||||
|
|
||||||
private static class InetAddressInternetAddress extends InternetAddress {
|
private static class InetAddressInternetAddress extends InternetAddress {
|
||||||
private final InetAddress inetAddress;
|
private final InetAddress inetAddress;
|
||||||
|
private final String raw;
|
||||||
|
|
||||||
protected InetAddressInternetAddress(String originalString, InetAddress inetAddress) {
|
protected InetAddressInternetAddress(String originalString, String raw, InetAddress inetAddress) {
|
||||||
super(originalString);
|
super(originalString);
|
||||||
|
this.raw = raw;
|
||||||
this.inetAddress = inetAddress;
|
this.inetAddress = inetAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,18 +131,27 @@ public abstract class InternetAddress implements CharSequence {
|
||||||
public InetAddress asInetAddress() {
|
public InetAddress asInetAddress() {
|
||||||
return inetAddress;
|
return inetAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getRaw() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Ipv4 extends InetAddressInternetAddress {
|
public static final class Ipv4 extends InetAddressInternetAddress {
|
||||||
|
|
||||||
private final Inet4Address inet4Address;
|
private final Inet4Address inet4Address;
|
||||||
|
|
||||||
private Ipv4(String originalString) {
|
private Ipv4(String originalString, String raw) {
|
||||||
this(originalString, InetAddressUtil.ipv4From(originalString));
|
this(originalString, raw, InetAddressUtil.ipv4From(originalString));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Ipv4(String originalString, Inet4Address inet4Address) {
|
private Ipv4(String originalString, Inet4Address inet4Address) {
|
||||||
super(originalString, inet4Address);
|
this(originalString, originalString, inet4Address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ipv4(String originalString, String raw, Inet4Address inet4Address) {
|
||||||
|
super(originalString, raw, inet4Address);
|
||||||
this.inet4Address = inet4Address;
|
this.inet4Address = inet4Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,12 +164,16 @@ public abstract class InternetAddress implements CharSequence {
|
||||||
|
|
||||||
private Inet6Address inet6Address;
|
private Inet6Address inet6Address;
|
||||||
|
|
||||||
private Ipv6(String originalString) {
|
private Ipv6(String originalString, String raw) {
|
||||||
this(originalString, InetAddressUtil.ipv6From(originalString));
|
this(originalString, raw, InetAddressUtil.ipv6From(originalString));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Ipv6(String originalString, Inet6Address inet6Address) {
|
private Ipv6(String originalString, Inet6Address inet6Address) {
|
||||||
super(originalString, inet6Address);
|
this(originalString, originalString, inet6Address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ipv6(String originalString, String raw, Inet6Address inet6Address) {
|
||||||
|
super(originalString, raw, inet6Address);
|
||||||
this.inet6Address = inet6Address;
|
this.inet6Address = inet6Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2019-2021 Florian Schmaus.
|
* Copyright 2003-2007 Jive Software, 2019-2023 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -580,8 +580,9 @@ public class PacketParserUtils {
|
||||||
switch (iqData.getType()) {
|
switch (iqData.getType()) {
|
||||||
case error:
|
case error:
|
||||||
// If an IQ packet wasn't created above, create an empty error IQ packet.
|
// If an IQ packet wasn't created above, create an empty error IQ packet.
|
||||||
iqPacket = new ErrorIQ(error);
|
iqPacket = ErrorIQ.builder(error, iqData).build();
|
||||||
break;
|
// The following return is simply to avoid setting iqData again below.
|
||||||
|
return iqPacket;
|
||||||
case result:
|
case result:
|
||||||
iqPacket = new EmptyResultIQ();
|
iqPacket = new EmptyResultIQ();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2021 Florian Schmaus
|
* Copyright © 2014-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -379,4 +379,12 @@ public class ParserUtils {
|
||||||
public static QName getQName(XmlPullParser parser) {
|
public static QName getQName(XmlPullParser parser) {
|
||||||
return parser.getQName();
|
return parser.getQName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InternetAddress getInternetAddressIngoringZoneIdAttribute(XmlPullParser parser, String attribute) {
|
||||||
|
String inetAddressString = parser.getAttributeValue(attribute);
|
||||||
|
if (inetAddressString == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return InternetAddress.fromIgnoringZoneId(inetAddressString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,56 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for DNS related tasks.
|
* Smack's API for DNS related tasks.
|
||||||
|
* <h2>DNSSEC and DANE</h2>
|
||||||
|
* <h3>About</h3>
|
||||||
|
* <p>
|
||||||
|
* DNSSEC (<a href="https://tools.ietf.org/html/rfc4033">RFC 4033</a>, and others) authenticates DNS answers, positive
|
||||||
|
* and negative ones. This means that if a DNS response secured by DNSSEC turns out to be authentic, then you can be
|
||||||
|
* sure that the domain either exists, and that the returned resource records (RRs) are the ones the domain owner
|
||||||
|
* authorized, or that the domain does not exists and that nobody tried to fake its non existence.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The tricky part is that an application using DNSSEC can not determine whether a domain uses DNSSEC, does not use
|
||||||
|
* DNSSEC or if someone downgraded your DNS query using DNSSEC to a response without DNSSEC.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* DANE (<a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>) allows the verification of a TLS certificate with
|
||||||
|
* information stored in the DNS system and secured by DNSSEC. Thus DANE requires DNSSEC.
|
||||||
|
* </p>
|
||||||
|
* <h3>Prerequisites</h3>
|
||||||
|
* <p>
|
||||||
|
* From the three DNS resolver providers (MiniDNS, javax, dnsjava) supported by Smack we currently only support DNSSEc
|
||||||
|
* with <a href="https://github.com/minidns/minidns">MiniDNS</a>. MiniDNS is the default resolver when smack-android is
|
||||||
|
* used. For other configurations, make sure to add smack-resolver-minidns to your dependencies and call
|
||||||
|
* `MiniDnsResolver.setup()` prior using Smack (e.g. in a `static {}` code block).
|
||||||
|
* </p>
|
||||||
|
* <h3>DNSSEC API</h3>
|
||||||
|
* <p>
|
||||||
|
* Smack's DNSSEC API is very simple. Just use
|
||||||
|
* {@link org.jivesoftware.smack.ConnectionConfiguration.Builder#setDnssecMode(org.jivesoftware.smack.ConnectionConfiguration.DnssecMode)}
|
||||||
|
* to enable DNSSEC. The argument, {@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode}, can be one of
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode#disabled}</li>
|
||||||
|
* <li>{@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode#needsDnssec}</li>
|
||||||
|
* <li>{@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode#needsDnssecAndDane}</li>
|
||||||
|
* </ul>
|
||||||
|
* The default is disabled.
|
||||||
|
* <p>
|
||||||
|
* If {@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode#needsDnssec} is used, then then Smack will only
|
||||||
|
* connect if the DNS results required to determine a host for the XMPP domain could be verified using DNSSEC.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If set to {@link org.jivesoftware.smack.ConnectionConfiguration.DnssecMode#needsDnssecAndDane}, then then DANE will
|
||||||
|
* be used to verify the XMPP service's TLS certificate if STARTTLS is used.
|
||||||
|
* </p>
|
||||||
|
* <h2>Best Practices</h2>
|
||||||
|
* <p>
|
||||||
|
* We recommend that applications using Smack's DNSSEC API do not ask the user if DNSSEC is avaialble. Instead they
|
||||||
|
* should check for DNSSEC suport on every connection attempt. Once DNSSEC support has been discovered, the application
|
||||||
|
* should use the `needsDnssec` mode for all future connection attempts. The same scheme can be applied when using DANE.
|
||||||
|
* This approach is similar to the scheme established by to <a href="https://tools.ietf.org/html/rfc6797">HTTP Strict
|
||||||
|
* Transport Security" (HSTS, RFC 6797</a>.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.util.dns;
|
package org.jivesoftware.smack.util.dns;
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Copyright © 2014 Florian Schmaus
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.jivesoftware.smack;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
|
||||||
import org.jivesoftware.smack.packet.TestIQ;
|
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class StanzaIdTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIqId() {
|
|
||||||
IQ iq1 = new TestIQ();
|
|
||||||
String iq1Id = iq1.getStanzaId();
|
|
||||||
assertTrue(StringUtils.isNotEmpty(iq1Id));
|
|
||||||
|
|
||||||
IQ iq2 = new TestIQ();
|
|
||||||
String iq2Id = iq2.getStanzaId();
|
|
||||||
assertTrue(StringUtils.isNotEmpty(iq2Id));
|
|
||||||
|
|
||||||
assertFalse(iq1Id.equals(iq2Id));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2023 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.packet;
|
||||||
|
|
||||||
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class IqTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIqErrorWithChildElement() {
|
||||||
|
IQ request = new TestIQ();
|
||||||
|
StanzaError error = StanzaError.getBuilder().setCondition(StanzaError.Condition.bad_request).build();
|
||||||
|
ErrorIQ errorIq = IQ.createErrorResponse(request, error);
|
||||||
|
|
||||||
|
String expected = "<iq xmlns='jabber:client' id='42' type='error'>"
|
||||||
|
+ "<error type='modify'><bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>"
|
||||||
|
+ "<test-iq xmlns='https://igniterealtime.org/projects/smack'/>"
|
||||||
|
+ "</iq>";
|
||||||
|
assertXmlSimilar(expected, errorIq.toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2019 Florian Schmaus
|
* Copyright © 2014-2023 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -21,11 +21,11 @@ import org.jivesoftware.smack.SmackConfiguration;
|
||||||
public class TestIQ extends SimpleIQ {
|
public class TestIQ extends SimpleIQ {
|
||||||
|
|
||||||
public TestIQ() {
|
public TestIQ() {
|
||||||
this(SmackConfiguration.SMACK_URL_STRING, "test-iq");
|
this("test-iq", SmackConfiguration.SMACK_URL_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestIQ(String element, String namespace) {
|
public TestIQ(String element, String namespace) {
|
||||||
super(element, namespace);
|
super(StanzaBuilder.buildIqData("42"), element, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2023 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class InternetAddressTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromIgnoringZoneId() {
|
||||||
|
assertInternetAddressEqualsIgnoringZoneId("fe80::641a:cdff:febd:d665", "fe80::641a:cdff:febd:d665%dummy0");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertInternetAddressEqualsIgnoringZoneId(String expected, String input) {
|
||||||
|
InternetAddress internetAddress = InternetAddress.fromIgnoringZoneId(input);
|
||||||
|
assertEquals(expected, internetAddress.toString());
|
||||||
|
assertEquals(input, internetAddress.getRaw());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2010 Jive Software.
|
* Copyright 2010 Jive Software, 2022 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,14 +17,15 @@
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
|
||||||
|
@ -127,13 +128,21 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendNonza(Nonza element) {
|
protected void sendInternal(TopLevelStreamElement element) {
|
||||||
queue.add(element);
|
queue.add(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza packet) {
|
protected void sendNonBlockingInternal(TopLevelStreamElement element) throws OutgoingQueueFullException {
|
||||||
queue.add(packet);
|
boolean enqueued = queue.add(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.logging.Logger;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A threaded dummy connection.
|
* A threaded dummy connection.
|
||||||
|
@ -40,10 +41,11 @@ public class ThreadedDummyConnection extends DummyConnection {
|
||||||
private volatile boolean timeout = false;
|
private volatile boolean timeout = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza packet) {
|
protected void sendInternal(TopLevelStreamElement element) {
|
||||||
super.sendStanzaInternal(packet);
|
super.sendInternal(element);
|
||||||
|
|
||||||
if (packet instanceof IQ && !timeout) {
|
if (element instanceof IQ && !timeout) {
|
||||||
|
IQ iq = (IQ) element;
|
||||||
timeout = false;
|
timeout = false;
|
||||||
// Set reply packet to match one being sent. We haven't started the
|
// Set reply packet to match one being sent. We haven't started the
|
||||||
// other thread yet so this is still safe.
|
// other thread yet so this is still safe.
|
||||||
|
@ -51,11 +53,11 @@ public class ThreadedDummyConnection extends DummyConnection {
|
||||||
|
|
||||||
// If no reply has been set via addIQReply, then we create a simple reply
|
// If no reply has been set via addIQReply, then we create a simple reply
|
||||||
if (replyPacket == null) {
|
if (replyPacket == null) {
|
||||||
replyPacket = IQ.createResultIQ((IQ) packet);
|
replyPacket = IQ.createResultIQ(iq);
|
||||||
replyQ.add(replyPacket);
|
replyQ.add(replyPacket);
|
||||||
}
|
}
|
||||||
replyPacket.setStanzaId(packet.getStanzaId());
|
replyPacket.setStanzaId(iq.getStanzaId());
|
||||||
replyPacket.setTo(packet.getFrom());
|
replyPacket.setTo(iq.getFrom());
|
||||||
if (replyPacket.getType() == null) {
|
if (replyPacket.getType() == null) {
|
||||||
replyPacket.setType(IQ.Type.result);
|
replyPacket.setType(IQ.Type.result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/package-info.java
|
../../../../../../../smack-java8-full/src/main/java/org/jivesoftware/smackx/package-info.java
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smack optional Debuggers.
|
* Smack optional Debuggers, which include {@link EnhancedDebugger} and {@link LiteDebugger}.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.debugger;
|
package org.jivesoftware.smackx.debugger;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/package-info.java
|
../../../../../../../smack-java8-full/src/main/java/org/jivesoftware/smackx/package-info.java
|
|
@ -22,7 +22,19 @@ import org.jivesoftware.smack.util.SHA1;
|
||||||
import org.hsluv.HUSLColorConverter;
|
import org.hsluv.HUSLColorConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of XEP-0392: Consistent Color Generation version 0.6.0.
|
* Smack API for Consistent Color Generation (XEP-0392).
|
||||||
|
* <p>
|
||||||
|
* Since XMPP can be used on multiple platforms at the same time, it might be a
|
||||||
|
* good idea to render given Strings like nicknames in the same color on all
|
||||||
|
* platforms to provide a consistent user experience.
|
||||||
|
* </p>
|
||||||
|
* <h2>Usage</h2>
|
||||||
|
*
|
||||||
|
* <h2>Color Deficiency Corrections</h2>
|
||||||
|
* <p>
|
||||||
|
* Some users might suffer from color vision deficiencies. To compensate those
|
||||||
|
* deficiencies, the API allows for color correction.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Paul Schaub
|
* @author Paul Schaub
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,5 +17,122 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smack's API for XEP-0332: HTTP over XMPP transport.
|
* Smack's API for XEP-0332: HTTP over XMPP transport.
|
||||||
|
* <h2 id="discover-hoxt-support">Discover HOXT support</h2>
|
||||||
|
* <p>
|
||||||
|
* <strong>Description</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Before using this extension you must ensure that your counterpart supports it also.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <strong>Usage</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Once you have your <em><strong>ServiceDiscoveryManager</strong></em> you will be able to discover information
|
||||||
|
* associated with an XMPP entity. To discover the information of a given XMPP entity send
|
||||||
|
* <strong>discoverInfo(entityID)</strong> to your <em><strong>ServiceDiscoveryManager</strong></em> where entityID is
|
||||||
|
* the ID of the entity. The message <strong>discoverInfo(entityID)</strong> will answer with an instance of
|
||||||
|
* <em><strong>DiscoverInfo</strong></em> that contains the discovered information.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <strong>Examples</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* In this example we can see how to check if the counterpart supports HOXT:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
|
||||||
|
* ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||||
|
* // Get the information of a given XMPP entity, where entityID is a Jid
|
||||||
|
* DiscoverInfo discoInfo = discoManager.discoverInfo(entityID);
|
||||||
|
* // Check if room is HOXT is supported
|
||||||
|
* boolean isSupported = discoInfo.containsFeature("urn:xmpp:http");</code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h2 id="iq-exchange">IQ exchange</h2>
|
||||||
|
* <p>
|
||||||
|
* <strong>Description</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* You can use IQ’s to perform HTTP requests and responses. This is applicable to relatively short requests and
|
||||||
|
* responses (due to the limitation of XMPP message size).
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <strong>Usage</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* First you need to register a <em><strong>StanzaListener</strong></em> to be able to handle intended IQs.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For the HTTP client you:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>You create and send <em><strong>HttpOverXmppReq</strong></em> request.</li>
|
||||||
|
* <li>Then you handle the <em><strong>HttpOverXmppResp</strong></em> response in your
|
||||||
|
* <em><strong>StanzaListener</strong></em>.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* For the HTTP server you:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>You handle the <em><strong>HttpOverXmppReq</strong></em> requests in your
|
||||||
|
* <em><strong>StanzaListener</strong></em>.</li>
|
||||||
|
* <li>And create and send <em><strong>HttpOverXmppResp</strong></em> responses.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* <strong>Examples</strong>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* In this example we are an HTTP client, so we send a request (POST) and handle the response:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* // create a request body
|
||||||
|
* String urlEncodedMessage = "I_love_you";
|
||||||
|
*
|
||||||
|
* // prepare headers
|
||||||
|
* List<Header> headers = new ArrayList<>();
|
||||||
|
* headers.add(new Header("Host", "juliet.capulet.com"));
|
||||||
|
* headers.add(new Header("Content-Type", "application/x-www-form-urlencoded"));
|
||||||
|
* headers.add(new Header("Content-Length", Integer.toString(urlEncodedMessage.length())));
|
||||||
|
*
|
||||||
|
* // provide body or request (not mandatory, - empty body is used for GET)
|
||||||
|
* AbstractHttpOverXmpp.Text child = new AbstractHttpOverXmpp.Text(urlEncodedMessage);
|
||||||
|
* AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child);
|
||||||
|
*
|
||||||
|
* // create request
|
||||||
|
* HttpOverXmppReq req = HttpOverXmppReq.buider()
|
||||||
|
* .setMethod(HttpMethod.POST)
|
||||||
|
* .setResource("/mailbox")
|
||||||
|
* .setHeaders(headers)
|
||||||
|
* .setVersion("1.1")
|
||||||
|
* .setData(data)
|
||||||
|
* .build();
|
||||||
|
*
|
||||||
|
* // add to, where jid is the Jid of the individual the packet is sent to
|
||||||
|
* req.setTo(jid);
|
||||||
|
*
|
||||||
|
* // send it
|
||||||
|
* connection.sendIqWithResponseCallback(req, new StanzaListener() {
|
||||||
|
* public void processStanza(Stanza iq) {
|
||||||
|
* HttpOverXmppResp resp = (HttpOverXmppResp) iq;
|
||||||
|
* // check HTTP response code
|
||||||
|
* if (resp.getStatusCode() == 200) {
|
||||||
|
* // get content of the response
|
||||||
|
* NamedElement child = resp.getData().getChild();
|
||||||
|
* // check which type of content of the response arrived
|
||||||
|
* if (child instanceof AbstractHttpOverXmpp.Xml) {
|
||||||
|
* // print the message and anxiously read if from the console ;)
|
||||||
|
* System.out.println(((AbstractHttpOverXmpp.Xml) child).getText());
|
||||||
|
* } else {
|
||||||
|
* // process other AbstractHttpOverXmpp data child subtypes
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.hoxt;
|
package org.jivesoftware.smackx.hoxt;
|
||||||
|
|
|
@ -16,6 +16,167 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smack's API for XMPP IoT.
|
* Smack's API for XMPP IoT (XEP-0323, -0324, -0325, -0347).
|
||||||
|
* <p>
|
||||||
|
* The Internet of Things (IoT) XEPs are an experimental open standard how XMPP
|
||||||
|
* can be used for IoT. They currently consists of
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>XEP-0323 Sensor Data</li>
|
||||||
|
* <li>XEP-0324 Provisioning</li>
|
||||||
|
* <li>XEP-0325 Control</li>
|
||||||
|
* <li>XEP-0326 Concentrators</li>
|
||||||
|
* <li>XEP-0347 Discovery</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Smack only supports a subset of the functionality described by the XEPs!
|
||||||
|
* </p>
|
||||||
|
* <h2>Thing Builder</h2>
|
||||||
|
* <p>
|
||||||
|
* The {@link org.jivesoftware.smackx.iot.Thing} class acts as basic entity
|
||||||
|
* representing a single "Thing" which can be used to retrieve data from or to
|
||||||
|
* send control commands to. `Things` are constructed using a builder API.
|
||||||
|
* </p>
|
||||||
|
* <h2>Reading data from things</h2>
|
||||||
|
* <p>
|
||||||
|
* For example, we can build a Thing which provides the current temperature with
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Thing dataThing = Thing.builder().setKey(key).setSerialNumber(sn).setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() {
|
||||||
|
* {@literal @}Override
|
||||||
|
* public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) {
|
||||||
|
* int temp = getCurrentTemperature();
|
||||||
|
* IoTDataField.IntField field = new IntField("temperature", temp);
|
||||||
|
* callback.momentaryReadOut(Collections.singletonList(field));
|
||||||
|
* }
|
||||||
|
* }).build();
|
||||||
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* While not strictly required, most things are identified via a key and serial
|
||||||
|
* number. We also build the thing with a "momentary read out request handler"
|
||||||
|
* which when triggered, retrieves the current temperature and reports it back
|
||||||
|
* to the requestor.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* After the `Thing` is built, it needs to be made available so that other
|
||||||
|
* entities within the federated XMPP network can use it. Right now we only
|
||||||
|
* install the Thing in the `IoTDataManager`, which means the thing will act on
|
||||||
|
* read out requests but not be managed by a provisioning server.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* IoTDataManager iotDataManager = IoTDataManager.getInstanceFor(connection);
|
||||||
|
* iotDataManager.installThing(thing);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* The data can be read out also by using the <code>IoTDataManager</code>:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* FullJid jid = …
|
||||||
|
* List<IoTFieldsExtension> values = iotDataManager.requestMomentaryValuesReadOut(jid);
|
||||||
|
* }</pre>
|
||||||
|
* <p>
|
||||||
|
* Now you have to unwrap the `IoTDataField` instances from the
|
||||||
|
* `IoTFieldsExtension`. Note that Smack currently only supports a subset of the
|
||||||
|
* specified data types.
|
||||||
|
* </p>
|
||||||
|
* <h2>Controlling a thing</h2>
|
||||||
|
* <p>
|
||||||
|
* Things can also be controlled, e.g. to turn on a light. Let's create a thing
|
||||||
|
* which can be used to turn the light on and off.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* Thing controlThing = Thing.builder().setKey(key).setSerialNumber(sn).setControlRequestHandler(new ThingControlRequest() {
|
||||||
|
* {@literal @}Override
|
||||||
|
* public void processRequest(Jid from, Collection<SetData>} setData) throws XMPPErrorException {
|
||||||
|
* for (final SetData data : setData) {
|
||||||
|
* if (!data.getName().equals("light")) continue;
|
||||||
|
* if (!(data instanceof SetBoolData)) continue;
|
||||||
|
* SetBoolData boolData = (SetBoolData) data;
|
||||||
|
* setLight(boolData.getBooleanValue());
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }).build();
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Now we have to install this thing into the `IoTControlManager`:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* IoTControlManager iotControlManager = IoTControlManager.getInstanceFor(connection);
|
||||||
|
* iotControlManager.installThing(thing);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* The `IoTControlManager` can also be used to control a thing:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* FullJid jid = …
|
||||||
|
* SetData setData = new SetBoolData("light", true);
|
||||||
|
* iotControlManager.setUsingIq(jid, setData);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Smack currently only supports a subset of the possible data types for set
|
||||||
|
* data.
|
||||||
|
* </p>
|
||||||
|
* <h2>Discovery</h2>
|
||||||
|
* <p>
|
||||||
|
* You may have wondered how a full JIDs of things can be determined. One
|
||||||
|
* approach is using the discovery mechanisms specified in XEP-0347. Smack
|
||||||
|
* provides the `IoTDiscoveryManager` as an API for this.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For example, instead of just installing the previous things in the
|
||||||
|
* `IoTDataManager` and/or `IoTControlManager`, we could also use the
|
||||||
|
* `IoTDiscoveryManger` to register the thing with a registry. Doing this also
|
||||||
|
* installs the thing in the `IoTDataManager` and the `IoTControlManager`.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* IoTDiscoveryManager iotDiscoveryManager = IoTDiscoveryManager.getInstanceFor(connection);
|
||||||
|
* iotDiscovyerManager.registerThing(thing);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* The registry will now make the thing known to a broader audience, and
|
||||||
|
* available for a potential owner.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The `IoTDiscoveryManager` can also be used to claim, disown, remove and
|
||||||
|
* unregister a thing.
|
||||||
|
* </p>
|
||||||
|
* <h2>Provisioning</h2>
|
||||||
|
* <p>
|
||||||
|
* Things can usually only be used by other things if they are friends. Since a
|
||||||
|
* thing normally can't decide on its own if an incoming friendship request
|
||||||
|
* should be granted or not, we can delegate this decision to a provisioning
|
||||||
|
* service. Smack provides the `IoTProvisinoManager` to deal with friendship and
|
||||||
|
* provisioning.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For example, if you want to befriend another thing:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* BareJid jid = …
|
||||||
|
* IoTProvisioningManager iotProvisioningManager = IoTProvisioningManager.getInstanceFor(connection);
|
||||||
|
* iotProvisioningManager.sendFriendshipRequest(jid);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.iot;
|
package org.jivesoftware.smackx.iot;
|
||||||
|
|
|
@ -237,7 +237,7 @@ public final class MamManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The the XMPP address of this MAM archive. Note that this method may return {@code null} if this MamManager
|
* the XMPP address of this MAM archive. Note that this method may return {@code null} if this MamManager
|
||||||
* handles the local entity's archive and if the connection has never been authenticated at least once.
|
* handles the local entity's archive and if the connection has never been authenticated at least once.
|
||||||
*
|
*
|
||||||
* @return the XMPP address of this MAM archive or {@code null}.
|
* @return the XMPP address of this MAM archive or {@code null}.
|
||||||
|
|