mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-25 15:52:06 +01:00
Add support for XEP-0198: Stream Management
- De-duplicate code by moving it into AbstractXMPPConnection - Introduce TopLevelStreamElement as superclass for all XMPP stream elements. - Add SynchronizationPoint, ParserUtils - Add ParserUtils Fixes SMACK-333 and SMACK-521
This commit is contained in:
parent
07c10a7444
commit
fc51f3df48
69 changed files with 3277 additions and 1083 deletions
|
@ -33,7 +33,7 @@ allprojects {
|
||||||
// build, causing unnecessary rebuilds.
|
// build, causing unnecessary rebuilds.
|
||||||
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
|
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
|
||||||
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
|
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
|
||||||
jxmppVersion = "0.2.0"
|
jxmppVersion = "0.3.0"
|
||||||
}
|
}
|
||||||
group = 'org.igniterealtime.smack'
|
group = 'org.igniterealtime.smack'
|
||||||
sourceCompatibility = 1.7
|
sourceCompatibility = 1.7
|
||||||
|
|
|
@ -9,6 +9,14 @@ for many of the protocol extensions.
|
||||||
This manual provides details about each of the "smackx" extensions, including
|
This manual provides details about each of the "smackx" extensions, including
|
||||||
what it is, how to use it, and some simple example code.
|
what it is, how to use it, and some simple example code.
|
||||||
|
|
||||||
|
Currently supported XEPs of smack-tcp
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
| Name | XEP | Description |
|
||||||
|
|---------------------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
|
||||||
|
| [Stream Management](streammanagement.html) | [XEP-0198](http://xmpp.org/extensions/xep-0198.html) | Allows active management of an XML Stream between two XMPP entities (stanza acknowledgement, stream resumption). |
|
||||||
|
|
||||||
|
|
||||||
Smack Extensions and currently supported XEPs by Smack (smack-extensions)
|
Smack Extensions and currently supported XEPs by Smack (smack-extensions)
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
37
documentation/extensions/streammanagement.md
Normal file
37
documentation/extensions/streammanagement.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
Stream Management
|
||||||
|
=================
|
||||||
|
|
||||||
|
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
|
25
resources/getCopyright.sh
Executable file
25
resources/getCopyright.sh
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPTDIR="$(dirname ${BASH_SOURCE[0]})"
|
||||||
|
BASEDIR=${SCRIPTDIR}/..
|
||||||
|
|
||||||
|
cd $BASEDIR
|
||||||
|
SUBPROJECTS=$(grep -oP "\'.*\'" settings.gradle | sed "s;';;g")
|
||||||
|
for p in $SUBPROJECTS; do
|
||||||
|
echo "Copyright notices for $p"
|
||||||
|
# Find all .java files in the project
|
||||||
|
find $p/src -type f -name "*.java" -print0 | \
|
||||||
|
# Get the project string
|
||||||
|
xargs -0 grep -ohP '^.*\* Copyright \K.*' | \
|
||||||
|
# Sort the output
|
||||||
|
sort | \
|
||||||
|
# Remove duplicates
|
||||||
|
uniq | \
|
||||||
|
# Split multi Copyright statemtents, e.g. "2001-2013 FooBar, 2014 Baz"
|
||||||
|
tr ',' '\n' | \
|
||||||
|
# Remove whitespaces resulting from the previous split
|
||||||
|
sed "s/^[ \t]*//" | \
|
||||||
|
# Remove dots at the end and '©' at the beginning
|
||||||
|
sed "s/^© //" | sed "s/\.$//" | sed "s/^(C) //"
|
||||||
|
echo -ne "\n"
|
||||||
|
done
|
|
@ -20,9 +20,8 @@ package org.jivesoftware.smack.bosh;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
@ -86,8 +85,6 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
||||||
final String challengeData = parser.nextText();
|
final String challengeData = parser.nextText();
|
||||||
connection.getSASLAuthentication()
|
connection.getSASLAuthentication()
|
||||||
.challengeReceived(challengeData);
|
.challengeReceived(challengeData);
|
||||||
connection.processPacket(new Challenge(
|
|
||||||
challengeData));
|
|
||||||
} else if (parser.getName().equals("success")) {
|
} else if (parser.getName().equals("success")) {
|
||||||
connection.send(ComposableBody.builder()
|
connection.send(ComposableBody.builder()
|
||||||
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
||||||
|
@ -100,14 +97,12 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
||||||
.build());
|
.build());
|
||||||
Success success = new Success(parser.nextText());
|
Success success = new Success(parser.nextText());
|
||||||
connection.getSASLAuthentication().authenticated(success);
|
connection.getSASLAuthentication().authenticated(success);
|
||||||
connection.processPacket(success);
|
|
||||||
} else if (parser.getName().equals("features")) {
|
} else if (parser.getName().equals("features")) {
|
||||||
parseFeatures(parser);
|
parseFeatures(parser);
|
||||||
} else if (parser.getName().equals("failure")) {
|
} else if (parser.getName().equals("failure")) {
|
||||||
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
|
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
|
||||||
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
|
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
|
||||||
connection.getSASLAuthentication().authenticationFailed(failure);
|
connection.getSASLAuthentication().authenticationFailed(failure);
|
||||||
connection.processPacket(failure);
|
|
||||||
}
|
}
|
||||||
} else if (parser.getName().equals("error")) {
|
} else if (parser.getName().equals("error")) {
|
||||||
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
|
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
|
||||||
|
@ -123,41 +118,7 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and setup the XML stream features.
|
|
||||||
*
|
|
||||||
* @param parser the XML parser, positioned at the start of a message packet.
|
|
||||||
* @throws Exception if an exception occurs while parsing the packet.
|
|
||||||
*/
|
|
||||||
private void parseFeatures(XmlPullParser parser) throws Exception {
|
private void parseFeatures(XmlPullParser parser) throws Exception {
|
||||||
boolean done = false;
|
connection.parseFeatures0(parser);
|
||||||
while (!done) {
|
|
||||||
int eventType = parser.next();
|
|
||||||
|
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
|
||||||
if (parser.getName().equals("mechanisms")) {
|
|
||||||
// The server is reporting available SASL mechanisms. Store
|
|
||||||
// this information
|
|
||||||
// which will be used later while logging (i.e.
|
|
||||||
// authenticating) into
|
|
||||||
// the server
|
|
||||||
connection.getSASLAuthentication().setAvailableSASLMethods(
|
|
||||||
PacketParserUtils.parseMechanisms(parser));
|
|
||||||
} else if (parser.getName().equals("bind")) {
|
|
||||||
// The server requires the client to bind a resource to the
|
|
||||||
// stream
|
|
||||||
connection.serverRequiresBinding();
|
|
||||||
} else if (parser.getName().equals("session")) {
|
|
||||||
// The server supports sessions
|
|
||||||
connection.serverSupportsSession();
|
|
||||||
} else if (parser.getName().equals("register")) {
|
|
||||||
connection.serverSupportsAccountCreation();
|
|
||||||
}
|
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
|
||||||
if (parser.getName().equals("features")) {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,12 @@ import org.jivesoftware.smack.ConnectionCreationListener;
|
||||||
import org.jivesoftware.smack.ConnectionListener;
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
import org.jivesoftware.smack.Roster;
|
import org.jivesoftware.smack.Roster;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Presence.Type;
|
import org.jivesoftware.smack.packet.Presence.Type;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.igniterealtime.jbosh.BOSHClient;
|
import org.igniterealtime.jbosh.BOSHClient;
|
||||||
import org.igniterealtime.jbosh.BOSHClientConfig;
|
import org.igniterealtime.jbosh.BOSHClientConfig;
|
||||||
import org.igniterealtime.jbosh.BOSHClientConnEvent;
|
import org.igniterealtime.jbosh.BOSHClientConnEvent;
|
||||||
|
@ -85,7 +88,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
// Some flags which provides some info about the current state.
|
// Some flags which provides some info about the current state.
|
||||||
private boolean connected = false;
|
private boolean connected = false;
|
||||||
private boolean authenticated = false;
|
private boolean authenticated = false;
|
||||||
private boolean anonymous = false;
|
|
||||||
private boolean isFirstInitialization = true;
|
private boolean isFirstInitialization = true;
|
||||||
private boolean wasAuthenticated = false;
|
private boolean wasAuthenticated = false;
|
||||||
private boolean done = false;
|
private boolean done = false;
|
||||||
|
@ -104,11 +106,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected String sessionID = null;
|
protected String sessionID = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The full JID of the authenticated user.
|
|
||||||
*/
|
|
||||||
private String user = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a HTTP Binding connection to a XMPP server.
|
* Create a HTTP Binding connection to a XMPP server.
|
||||||
*
|
*
|
||||||
|
@ -220,10 +217,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAnonymous() {
|
|
||||||
return anonymous;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAuthenticated() {
|
public boolean isAuthenticated() {
|
||||||
return authenticated;
|
return authenticated;
|
||||||
}
|
}
|
||||||
|
@ -250,6 +243,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
throw new AlreadyLoggedInException();
|
throw new AlreadyLoggedInException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait with SASL auth until the SASL mechanisms have been received
|
||||||
|
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||||
|
|
||||||
// Do partial version of nameprep on the username.
|
// Do partial version of nameprep on the username.
|
||||||
username = username.toLowerCase(Locale.US).trim();
|
username = username.toLowerCase(Locale.US).trim();
|
||||||
|
|
||||||
|
@ -264,41 +261,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
throw new SaslException("No non-anonymous SASL authentication mechanism available");
|
throw new SaslException("No non-anonymous SASL authentication mechanism available");
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = bindResourceAndEstablishSession(resource);
|
bindResourceAndEstablishSession(resource);
|
||||||
// Set the user.
|
|
||||||
if (response != null) {
|
|
||||||
this.user = response;
|
|
||||||
// Update the serviceName with the one returned by the server
|
|
||||||
setServiceName(response);
|
|
||||||
} else {
|
|
||||||
this.user = username + "@" + getServiceName();
|
|
||||||
if (resource != null) {
|
|
||||||
this.user += "/" + resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate that we're now authenticated.
|
// Stores the authentication for future reconnection
|
||||||
authenticated = true;
|
|
||||||
anonymous = false;
|
|
||||||
|
|
||||||
// Stores the autentication for future reconnection
|
|
||||||
setLoginInfo(username, password, resource);
|
setLoginInfo(username, password, resource);
|
||||||
|
afterSuccessfulLogin(false, false);
|
||||||
// If debugging is enabled, change the the debug window title to include
|
|
||||||
// the
|
|
||||||
// name we are now logged-in as.l
|
|
||||||
if (config.isDebuggerEnabled() && debugger != null) {
|
|
||||||
debugger.userHasLogged(user);
|
|
||||||
}
|
|
||||||
callConnectionAuthenticatedListener();
|
|
||||||
|
|
||||||
// Set presence to online. It is important that this is done after
|
|
||||||
// callConnectionAuthenticatedListener(), as this call will also
|
|
||||||
// eventually load the roster. And we should load the roster before we
|
|
||||||
// send the initial presence.
|
|
||||||
if (config.isSendPresence()) {
|
|
||||||
sendPacket(new Presence(Presence.Type.available));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loginAnonymously() throws XMPPException, SmackException, IOException {
|
public void loginAnonymously() throws XMPPException, SmackException, IOException {
|
||||||
|
@ -309,6 +276,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
throw new AlreadyLoggedInException();
|
throw new AlreadyLoggedInException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait with SASL auth until the SASL mechanisms have been received
|
||||||
|
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||||
|
|
||||||
if (saslAuthentication.hasAnonymousAuthentication()) {
|
if (saslAuthentication.hasAnonymousAuthentication()) {
|
||||||
saslAuthentication.authenticateAnonymously();
|
saslAuthentication.authenticateAnonymously();
|
||||||
}
|
}
|
||||||
|
@ -317,38 +287,27 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
throw new SaslException("No anonymous SASL authentication mechanism available");
|
throw new SaslException("No anonymous SASL authentication mechanism available");
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = bindResourceAndEstablishSession(null);
|
bindResourceAndEstablishSession(null);
|
||||||
// Set the user value.
|
|
||||||
this.user = response;
|
|
||||||
// Update the serviceName with the one returned by the server
|
|
||||||
setServiceName(response);
|
|
||||||
|
|
||||||
// Set presence to online.
|
afterSuccessfulLogin(true, false);
|
||||||
if (config.isSendPresence()) {
|
}
|
||||||
sendPacket(new Presence(Presence.Type.available));
|
|
||||||
|
@Override
|
||||||
|
public void send(PlainStreamElement element) throws NotConnectedException {
|
||||||
|
if (done) {
|
||||||
|
throw new NotConnectedException();
|
||||||
}
|
}
|
||||||
|
sendElement(element);
|
||||||
// Indicate that we're now authenticated.
|
|
||||||
authenticated = true;
|
|
||||||
anonymous = true;
|
|
||||||
|
|
||||||
// If debugging is enabled, change the the debug window title to include the
|
|
||||||
// name we are now logged-in as.
|
|
||||||
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
|
|
||||||
// will be null
|
|
||||||
if (config.isDebuggerEnabled() && debugger != null) {
|
|
||||||
debugger.userHasLogged(user);
|
|
||||||
}
|
|
||||||
callConnectionAuthenticatedListener();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendPacketInternal(Packet packet) throws NotConnectedException {
|
protected void sendPacketInternal(Packet packet) throws NotConnectedException {
|
||||||
if (done) {
|
sendElement(packet);
|
||||||
throw new NotConnectedException();
|
}
|
||||||
}
|
|
||||||
|
private void sendElement(Element element) {
|
||||||
try {
|
try {
|
||||||
send(ComposableBody.builder().setPayloadXML(packet.toXML().toString()).build());
|
send(ComposableBody.builder().setPayloadXML(element.toXML().toString()).build());
|
||||||
}
|
}
|
||||||
catch (BOSHException e) {
|
catch (BOSHException e) {
|
||||||
LOGGER.log(Level.SEVERE, "BOSHException in sendPacketInternal", e);
|
LOGGER.log(Level.SEVERE, "BOSHException in sendPacketInternal", e);
|
||||||
|
@ -524,19 +483,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
return super.getSASLAuthentication();
|
return super.getSASLAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void parseFeatures0(XmlPullParser parser) throws Exception {
|
||||||
protected void serverRequiresBinding() {
|
parseFeatures(parser);
|
||||||
super.serverRequiresBinding();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void serverSupportsSession() {
|
|
||||||
super.serverSupportsSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void serverSupportsAccountCreation() {
|
|
||||||
super.serverSupportsAccountCreation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
@ -28,27 +29,43 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
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.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
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.ConnectionException;
|
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
|
import org.jivesoftware.smack.compress.packet.Compress;
|
||||||
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
|
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
|
||||||
import org.jivesoftware.smack.debugger.SmackDebugger;
|
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketFilter;
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketIDFilter;
|
import org.jivesoftware.smack.filter.PacketIDFilter;
|
||||||
import org.jivesoftware.smack.packet.Bind;
|
import org.jivesoftware.smack.packet.Bind;
|
||||||
|
import org.jivesoftware.smack.packet.CapsExtension;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.Mechanisms;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.jivesoftware.smack.packet.Registration;
|
||||||
|
import org.jivesoftware.smack.packet.RosterVer;
|
||||||
import org.jivesoftware.smack.packet.Session;
|
import org.jivesoftware.smack.packet.Session;
|
||||||
|
import org.jivesoftware.smack.packet.StartTls;
|
||||||
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
|
||||||
public abstract class AbstractXMPPConnection implements XMPPConnection {
|
public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName());
|
||||||
|
@ -105,6 +122,15 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
protected final Map<PacketInterceptor, InterceptorWrapper> interceptors =
|
protected final Map<PacketInterceptor, InterceptorWrapper> interceptors =
|
||||||
new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>();
|
new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>();
|
||||||
|
|
||||||
|
protected final Lock connectionLock = new ReentrantLock();
|
||||||
|
|
||||||
|
protected final Map<String, PacketExtension> streamFeatures = new HashMap<String, PacketExtension>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full JID of the authenticated user.
|
||||||
|
*/
|
||||||
|
protected String user;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -125,6 +151,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected Writer writer;
|
protected Writer writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to success if the last features stanza from the server has been parsed. A XMPP connection
|
||||||
|
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
|
||||||
|
* stanza is send by the server. This is set to true once the last feature stanza has been
|
||||||
|
* parsed.
|
||||||
|
*/
|
||||||
|
protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>(
|
||||||
|
AbstractXMPPConnection.this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to success if the sasl feature has been received.
|
||||||
|
*/
|
||||||
|
protected final SynchronizationPoint<SmackException> saslFeatureReceived = new SynchronizationPoint<SmackException>(
|
||||||
|
AbstractXMPPConnection.this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
||||||
|
@ -142,21 +182,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected final ConnectionConfiguration config;
|
protected final ConnectionConfiguration config;
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the Caps Node information for the used XMPP service (i.e. the XMPP server)
|
|
||||||
*/
|
|
||||||
private String serviceCapsNode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how the from attribute of outgoing stanzas should be handled.
|
* Defines how the from attribute of outgoing stanzas should be handled.
|
||||||
*/
|
*/
|
||||||
private FromMode fromMode = FromMode.OMITTED;
|
private FromMode fromMode = FromMode.OMITTED;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores whether the server supports rosterVersioning
|
|
||||||
*/
|
|
||||||
private boolean rosterVersioningSupported = false;
|
|
||||||
|
|
||||||
protected XMPPInputOutputStream compressionHandler;
|
protected XMPPInputOutputStream compressionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,22 +230,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected int port;
|
protected int port;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set to true if the server requires the connection to be binded in order to continue.
|
|
||||||
* <p>
|
|
||||||
* Note that we use AtomicBoolean here because it allows us to set the Boolean *object*, which
|
|
||||||
* we also use as synchronization object. A plain non-atomic Boolean object would be newly created
|
|
||||||
* for every change of the boolean value, which makes it useless as object for wait()/notify().
|
|
||||||
*/
|
|
||||||
private AtomicBoolean bindingRequired = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private boolean sessionSupported;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private Exception connectionException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag that indicates if the user is currently authenticated with the server.
|
* Flag that indicates if the user is currently authenticated with the server.
|
||||||
*/
|
*/
|
||||||
|
@ -227,6 +241,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected boolean wasAuthenticated = false;
|
protected boolean wasAuthenticated = false;
|
||||||
|
|
||||||
|
private boolean anonymous = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new XMPPConnection to a XMPP server.
|
* Create a new XMPPConnection to a XMPP server.
|
||||||
*
|
*
|
||||||
|
@ -267,14 +283,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isAuthenticated();
|
public abstract boolean isAuthenticated();
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract boolean isAnonymous();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isSecureConnection();
|
public abstract boolean isSecureConnection();
|
||||||
|
|
||||||
protected abstract void sendPacketInternal(Packet packet) throws NotConnectedException;
|
protected abstract void sendPacketInternal(Packet packet) throws NotConnectedException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void send(PlainStreamElement element) throws NotConnectedException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isUsingCompression();
|
public abstract boolean isUsingCompression();
|
||||||
|
|
||||||
|
@ -292,21 +308,15 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
public void connect() throws SmackException, IOException, XMPPException {
|
public void connect() throws SmackException, IOException, XMPPException {
|
||||||
saslAuthentication.init();
|
saslAuthentication.init();
|
||||||
bindingRequired.set(false);
|
saslFeatureReceived.init();
|
||||||
sessionSupported = false;
|
lastFeaturesReceived.init();
|
||||||
connectionException = null;
|
|
||||||
connectInternal();
|
connectInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract method that concrete subclasses of XMPPConnection need to implement to perform their
|
* Abstract method that concrete subclasses of XMPPConnection need to implement to perform their
|
||||||
* way of XMPP connection establishment. Implementations must guarantee that this method will
|
* way of XMPP connection establishment. Implementations are required to perform an automatic
|
||||||
* block until the last features stanzas has been parsed and the features have been reported
|
* login if the previous connection state was logged (authenticated).
|
||||||
* back to XMPPConnection (e.g. by calling @{link {@link AbstractXMPPConnection#serverRequiresBinding()}
|
|
||||||
* and such).
|
|
||||||
* <p>
|
|
||||||
* Also implementations are required to perform an automatic login if the previous connection
|
|
||||||
* state was logged (authenticated).
|
|
||||||
*
|
*
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
@ -383,44 +393,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
|
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
|
||||||
|
|
||||||
/**
|
protected void bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
||||||
* Notification message saying that the server requires the client to bind a
|
IOException, SmackException {
|
||||||
* resource to the stream.
|
|
||||||
*/
|
|
||||||
protected void serverRequiresBinding() {
|
|
||||||
synchronized (bindingRequired) {
|
|
||||||
bindingRequired.set(true);
|
|
||||||
bindingRequired.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Wait until either:
|
||||||
* Notification message saying that the server supports sessions. When a server supports
|
// - the servers last features stanza has been parsed
|
||||||
* sessions the client needs to send a Session packet after successfully binding a resource
|
// - the timeout occurs
|
||||||
* for the session.
|
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||||
*/
|
lastFeaturesReceived.checkIfSuccessOrWait();
|
||||||
protected void serverSupportsSession() {
|
|
||||||
sessionSupported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
|
||||||
ResourceBindingNotOfferedException, NoResponseException, NotConnectedException {
|
|
||||||
|
|
||||||
synchronized (bindingRequired) {
|
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||||
if (!bindingRequired.get()) {
|
// Server never offered resource binding, which is REQURIED in XMPP client and
|
||||||
try {
|
// server implementations as per RFC6120 7.2
|
||||||
bindingRequired.wait(getPacketReplyTimeout());
|
throw new ResourceBindingNotOfferedException();
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
if (!bindingRequired.get()) {
|
|
||||||
// Server never offered resource binding, which is REQURIED in XMPP client and
|
|
||||||
// server
|
|
||||||
// implementations as per RFC6120 7.2
|
|
||||||
throw new ResourceBindingNotOfferedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource binding, see RFC6120 7.
|
// Resource binding, see RFC6120 7.
|
||||||
|
@ -435,9 +421,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
Bind response = packetCollector.nextResultOrThrow();
|
Bind response = packetCollector.nextResultOrThrow();
|
||||||
String userJID = response.getJid();
|
user = response.getJid();
|
||||||
|
setServiceName(XmppStringUtils.parseDomain(user));
|
||||||
|
|
||||||
if (sessionSupported && !getConfiguration().isLegacySessionDisabled()) {
|
if (hasFeature(Session.ELEMENT, Session.NAMESPACE) && !getConfiguration().isLegacySessionDisabled()) {
|
||||||
Session session = new Session();
|
Session session = new Session();
|
||||||
packetCollector = createPacketCollector(new PacketIDFilter(session));
|
packetCollector = createPacketCollector(new PacketIDFilter(session));
|
||||||
try {
|
try {
|
||||||
|
@ -448,33 +435,34 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
packetCollector.nextResultOrThrow();
|
packetCollector.nextResultOrThrow();
|
||||||
}
|
}
|
||||||
return userJID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setConnectionException(Exception e) {
|
protected void afterSuccessfulLogin(final boolean anonymous, final boolean resumed) throws NotConnectedException {
|
||||||
connectionException = e;
|
// Indicate that we're now authenticated.
|
||||||
}
|
this.authenticated = true;
|
||||||
|
this.anonymous = anonymous;
|
||||||
|
|
||||||
protected void throwConnectionExceptionOrNoResponse() throws IOException, NoResponseException, SmackException {
|
// If debugging is enabled, change the the debug window title to include the
|
||||||
if (connectionException != null) {
|
// name we are now logged-in as.
|
||||||
if (connectionException instanceof IOException) {
|
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
|
||||||
throw (IOException) connectionException;
|
// will be null
|
||||||
} else if (connectionException instanceof SmackException) {
|
if (config.isDebuggerEnabled() && debugger != null) {
|
||||||
throw (SmackException) connectionException;
|
debugger.userHasLogged(user);
|
||||||
} else {
|
}
|
||||||
throw new SmackException(connectionException);
|
callConnectionAuthenticatedListener();
|
||||||
}
|
|
||||||
} else {
|
// Set presence to online. It is important that this is done after
|
||||||
throw new NoResponseException();
|
// callConnectionAuthenticatedListener(), as this call will also
|
||||||
|
// eventually load the roster. And we should load the roster before we
|
||||||
|
// send the initial presence.
|
||||||
|
if (config.isSendPresence() && !resumed) {
|
||||||
|
sendPacket(new Presence(Presence.Type.available));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Reader getReader() {
|
@Override
|
||||||
return reader;
|
public boolean isAnonymous() {
|
||||||
}
|
return anonymous;
|
||||||
|
|
||||||
protected Writer getWriter() {
|
|
||||||
return writer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setServiceName(String serviceName) {
|
protected void setServiceName(String serviceName) {
|
||||||
|
@ -485,21 +473,21 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
config.setLoginInfo(username, password, resource);
|
config.setLoginInfo(username, password, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void serverSupportsAccountCreation() {
|
|
||||||
AccountManager.getInstance(this).setSupportsAccountCreation(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void maybeResolveDns() throws Exception {
|
protected void maybeResolveDns() throws Exception {
|
||||||
config.maybeResolveDns();
|
config.maybeResolveDns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Lock getConnectionLock() {
|
||||||
|
return connectionLock;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacket(Packet packet) throws NotConnectedException {
|
public void sendPacket(Packet packet) throws NotConnectedException {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
throw new NotConnectedException();
|
throw new NotConnectedException();
|
||||||
}
|
}
|
||||||
if (packet == null) {
|
if (packet == null) {
|
||||||
throw new NullPointerException("Packet is null.");
|
throw new IllegalArgumentException("Packet must not be null");
|
||||||
}
|
}
|
||||||
switch (fromMode) {
|
switch (fromMode) {
|
||||||
case OMITTED:
|
case OMITTED:
|
||||||
|
@ -800,35 +788,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the servers Entity Caps node
|
|
||||||
*
|
|
||||||
* XMPPConnection holds this information in order to avoid a dependency to
|
|
||||||
* smack-extensions where EntityCapsManager lives from smack.
|
|
||||||
*
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
protected void setServiceCapsNode(String node) {
|
|
||||||
serviceCapsNode = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServiceCapsNode() {
|
|
||||||
return serviceCapsNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRosterVersioningSupported() {
|
|
||||||
return rosterVersioningSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the server supports roster versioning as defined in XEP-0237.
|
|
||||||
*/
|
|
||||||
protected void setRosterVersioningSupported() {
|
|
||||||
rosterVersioningSupported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPacketReplyTimeout() {
|
public long getPacketReplyTimeout() {
|
||||||
return packetReplyTimeout;
|
return packetReplyTimeout;
|
||||||
|
@ -1055,6 +1014,112 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return config.isRosterLoadedAtLogin();
|
return config.isRosterLoadedAtLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final void parseFeatures(XmlPullParser parser) throws XmlPullParserException,
|
||||||
|
IOException, SecurityRequiredException, NotConnectedException {
|
||||||
|
streamFeatures.clear();
|
||||||
|
final int initialDepth = parser.getDepth();
|
||||||
|
while (true) {
|
||||||
|
int eventType = parser.next();
|
||||||
|
|
||||||
|
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) {
|
||||||
|
String name = parser.getName();
|
||||||
|
String namespace = parser.getNamespace();
|
||||||
|
switch (name) {
|
||||||
|
case StartTls.ELEMENT:
|
||||||
|
StartTls startTls = PacketParserUtils.parseStartTlsFeature(parser);
|
||||||
|
addStreamFeature(startTls);
|
||||||
|
break;
|
||||||
|
case Mechanisms.ELEMENT:
|
||||||
|
Mechanisms mechanisms = new Mechanisms(PacketParserUtils.parseMechanisms(parser));
|
||||||
|
addStreamFeature(mechanisms);
|
||||||
|
break;
|
||||||
|
case Bind.ELEMENT:
|
||||||
|
addStreamFeature(Bind.Feature.INSTANCE);
|
||||||
|
break;
|
||||||
|
case CapsExtension.ELEMENT:
|
||||||
|
// Set the entity caps node for the server if one is send
|
||||||
|
// See http://xmpp.org/extensions/xep-0115.html#stream
|
||||||
|
String node = parser.getAttributeValue(null, "node");
|
||||||
|
String ver = parser.getAttributeValue(null, "ver");
|
||||||
|
String hash = parser.getAttributeValue(null, "hash");
|
||||||
|
CapsExtension capsExtension = new CapsExtension(node, ver, hash);
|
||||||
|
addStreamFeature(capsExtension);
|
||||||
|
break;
|
||||||
|
case Session.ELEMENT:
|
||||||
|
addStreamFeature(Session.Feature.INSTANCE);
|
||||||
|
break;
|
||||||
|
case RosterVer.ELEMENT:
|
||||||
|
if(namespace.equals(RosterVer.NAMESPACE)) {
|
||||||
|
addStreamFeature(RosterVer.INSTANCE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOGGER.severe("Unkown Roster Versioning Namespace: "
|
||||||
|
+ namespace
|
||||||
|
+ ". Roster versioning not enabled");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Compress.Feature.ELEMENT:
|
||||||
|
Compress.Feature compression = PacketParserUtils.parseCompressionFeature(parser);
|
||||||
|
addStreamFeature(compression);
|
||||||
|
break;
|
||||||
|
case Registration.Feature.ELEMENT:
|
||||||
|
addStreamFeature(Registration.Feature.INSTANCE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
parseFeaturesSubclass(name, namespace, parser);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initialDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) {
|
||||||
|
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
|
||||||
|
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|
||||||
|
|| config.getSecurityMode() == SecurityMode.disabled) {
|
||||||
|
saslFeatureReceived.reportSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server reported the bind feature then we are that that we did SASL and maybe
|
||||||
|
// STARTTLS. We can then report that the last 'stream:features' have been parsed
|
||||||
|
if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||||
|
if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
|
||||||
|
|| !config.isCompressionEnabled()) {
|
||||||
|
// This was was last features from the server is either it did not contain
|
||||||
|
// compression or if we disabled it
|
||||||
|
lastFeaturesReceived.reportSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterFeaturesReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseFeaturesSubclass (String name, String namespace, XmlPullParser parser) {
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void afterFeaturesReceived() throws SecurityRequiredException, NotConnectedException {
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <F extends PacketExtension> F getFeature(String element, String namespace) {
|
||||||
|
return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFeature(String element, String namespace) {
|
||||||
|
return getFeature(element, namespace) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addStreamFeature(PacketExtension feature) {
|
||||||
|
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
|
||||||
|
streamFeatures.put(key, feature);
|
||||||
|
}
|
||||||
|
|
||||||
private final ScheduledExecutorService removeCallbacksService = new ScheduledThreadPoolExecutor(1,
|
private final ScheduledExecutorService removeCallbacksService = new ScheduledThreadPoolExecutor(1,
|
||||||
new SmackExecutorThreadFactory(connectionCounterValue));
|
new SmackExecutorThreadFactory(connectionCounterValue));
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.filter.AndFilter;
|
import org.jivesoftware.smack.filter.AndFilter;
|
||||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
|
||||||
import org.jivesoftware.smack.filter.IQTypeFilter;
|
import org.jivesoftware.smack.filter.IQTypeFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketFilter;
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
|
@ -45,6 +44,7 @@ import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.RosterPacket;
|
import org.jivesoftware.smack.packet.RosterPacket;
|
||||||
|
import org.jivesoftware.smack.packet.RosterVer;
|
||||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||||
import org.jxmpp.util.XmppStringUtils;
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
@ -227,12 +227,15 @@ public class Roster {
|
||||||
}
|
}
|
||||||
|
|
||||||
RosterPacket packet = new RosterPacket();
|
RosterPacket packet = new RosterPacket();
|
||||||
if (rosterStore != null && connection.isRosterVersioningSupported()) {
|
if (rosterStore != null && isRosterVersioningSupported()) {
|
||||||
packet.setVersion(rosterStore.getRosterVersion());
|
packet.setVersion(rosterStore.getRosterVersion());
|
||||||
}
|
}
|
||||||
PacketFilter filter = new IQReplyFilter(packet, connection);
|
connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
|
||||||
connection.addPacketListener(new RosterResultListener(), filter);
|
@Override
|
||||||
connection.sendPacket(packet);
|
public void processException(Exception exception) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Exception reloading roster" , exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -791,6 +794,10 @@ public class Roster {
|
||||||
|| item.getItemType().equals(RosterPacket.ItemType.both);
|
|| item.getItemType().equals(RosterPacket.ItemType.both);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isRosterVersioningSupported() {
|
||||||
|
return connection.hasFeature(RosterVer.ELEMENT, RosterVer.NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration for the subscription mode options.
|
* An enumeration for the subscription mode options.
|
||||||
*/
|
*/
|
||||||
|
@ -939,23 +946,13 @@ public class Roster {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the case of the empty IQ-result for roster versioning.
|
* Handles roster reults as described in RFC 6121 2.1.4
|
||||||
*
|
|
||||||
* Intended to listen for a concrete roster result and deregisters
|
|
||||||
* itself after a processed packet.
|
|
||||||
*/
|
*/
|
||||||
private class RosterResultListener implements PacketListener {
|
private class RosterResultListener implements PacketListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processPacket(Packet packet) {
|
public void processPacket(Packet packet) {
|
||||||
connection.removePacketListener(this);
|
LOGGER.fine("RosterResultListener received stanza");
|
||||||
|
|
||||||
IQ result = (IQ)packet;
|
|
||||||
if (!result.getType().equals(IQ.Type.result)) {
|
|
||||||
LOGGER.severe("Roster result IQ not of type result. Packet: " + result.toXML());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<String> addedEntries = new ArrayList<String>();
|
Collection<String> addedEntries = new ArrayList<String>();
|
||||||
Collection<String> updatedEntries = new ArrayList<String>();
|
Collection<String> updatedEntries = new ArrayList<String>();
|
||||||
Collection<String> deletedEntries = new ArrayList<String>();
|
Collection<String> deletedEntries = new ArrayList<String>();
|
||||||
|
|
|
@ -19,17 +19,17 @@ package org.jivesoftware.smack;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
|
import org.jivesoftware.smack.packet.Mechanisms;
|
||||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
||||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -128,13 +128,12 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AbstractXMPPConnection connection;
|
private final AbstractXMPPConnection connection;
|
||||||
private Collection<String> serverMechanisms = new ArrayList<String>();
|
|
||||||
private SASLMechanism currentMechanism = null;
|
private SASLMechanism currentMechanism = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean indicating if SASL negotiation has finished and was successful.
|
* Boolean indicating if SASL negotiation has finished and was successful.
|
||||||
*/
|
*/
|
||||||
private boolean saslNegotiated;
|
private boolean authenticationSuccessful;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Either of type {@link SmackException} or {@link SASLErrorException}
|
* Either of type {@link SmackException} or {@link SASLErrorException}
|
||||||
|
@ -152,7 +151,7 @@ public class SASLAuthentication {
|
||||||
* @return true if the server offered ANONYMOUS SASL as a way to authenticate users.
|
* @return true if the server offered ANONYMOUS SASL as a way to authenticate users.
|
||||||
*/
|
*/
|
||||||
public boolean hasAnonymousAuthentication() {
|
public boolean hasAnonymousAuthentication() {
|
||||||
return serverMechanisms.contains("ANONYMOUS");
|
return serverMechanisms().contains("ANONYMOUS");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,7 +160,7 @@ public class SASLAuthentication {
|
||||||
* @return true if the server offered SASL authentication besides ANONYMOUS SASL.
|
* @return true if the server offered SASL authentication besides ANONYMOUS SASL.
|
||||||
*/
|
*/
|
||||||
public boolean hasNonAnonymousAuthentication() {
|
public boolean hasNonAnonymousAuthentication() {
|
||||||
return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication());
|
return !serverMechanisms().isEmpty() && (serverMechanisms().size() != 1 || !hasAnonymousAuthentication());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,7 +196,7 @@ public class SASLAuthentication {
|
||||||
|
|
||||||
maybeThrowException();
|
maybeThrowException();
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!authenticationSuccessful) {
|
||||||
throw new NoResponseException();
|
throw new NoResponseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,7 +243,7 @@ public class SASLAuthentication {
|
||||||
|
|
||||||
maybeThrowException();
|
maybeThrowException();
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!authenticationSuccessful) {
|
||||||
throw new NoResponseException();
|
throw new NoResponseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,7 +282,7 @@ public class SASLAuthentication {
|
||||||
|
|
||||||
maybeThrowException();
|
maybeThrowException();
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!authenticationSuccessful) {
|
||||||
throw new NoResponseException();
|
throw new NoResponseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,17 +298,6 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Sets the available SASL mechanism reported by the server. The server will report the
|
|
||||||
* available SASL mechanism once the TLS negotiation was successful. This information is
|
|
||||||
* stored and will be used when doing the authentication for logging in the user.
|
|
||||||
*
|
|
||||||
* @param mechanisms collection of strings with the available SASL mechanism reported
|
|
||||||
* by the server.
|
|
||||||
*/
|
|
||||||
public void setAvailableSASLMethods(Collection<String> mechanisms) {
|
|
||||||
this.serverMechanisms = mechanisms;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
|
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
|
||||||
|
@ -355,7 +343,7 @@ public class SASLAuthentication {
|
||||||
if (success.getData() != null) {
|
if (success.getData() != null) {
|
||||||
challengeReceived(success.getData(), true);
|
challengeReceived(success.getData(), true);
|
||||||
}
|
}
|
||||||
saslNegotiated = true;
|
authenticationSuccessful = true;
|
||||||
// Wake up the thread that is waiting in the #authenticate method
|
// Wake up the thread that is waiting in the #authenticate method
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
notify();
|
notify();
|
||||||
|
@ -381,13 +369,17 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean authenticationSuccessful() {
|
||||||
|
return authenticationSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the internal state in order to be able to be reused. The authentication
|
* Initializes the internal state in order to be able to be reused. The authentication
|
||||||
* is used by the connection at the first login and then reused after the connection
|
* is used by the connection at the first login and then reused after the connection
|
||||||
* is disconnected and then reconnected.
|
* is disconnected and then reconnected.
|
||||||
*/
|
*/
|
||||||
protected void init() {
|
protected void init() {
|
||||||
saslNegotiated = false;
|
authenticationSuccessful = false;
|
||||||
saslException = null;
|
saslException = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +396,7 @@ public class SASLAuthentication {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (serverMechanisms.contains(mechanismName)) {
|
if (serverMechanisms().contains(mechanismName)) {
|
||||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||||
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
||||||
break;
|
break;
|
||||||
|
@ -412,4 +404,9 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
return selectedMechanism;
|
return selectedMechanism;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> serverMechanisms() {
|
||||||
|
Mechanisms mechanisms = connection.getFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE);
|
||||||
|
return mechanisms.getMechanisms();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,17 @@ public class SmackException extends Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AlreadyConnectedException extends SmackException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 5011416918049135231L;
|
||||||
|
|
||||||
|
public AlreadyConnectedException() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class NotConnectedException extends SmackException {
|
public static class NotConnectedException extends SmackException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,6 +131,10 @@ public class SmackException extends Exception {
|
||||||
|
|
||||||
public SecurityRequiredException() {
|
public SecurityRequiredException() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SecurityRequiredException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SecurityNotPossibleException extends SmackException {
|
public static class SecurityNotPossibleException extends SmackException {
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
|
|
||||||
|
public class SynchronizationPoint<E extends Exception> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SynchronizationPoint.class.getName());
|
||||||
|
|
||||||
|
private final AbstractXMPPConnection connection;
|
||||||
|
private final Lock connectionLock;
|
||||||
|
private final Condition condition;
|
||||||
|
|
||||||
|
private State state;
|
||||||
|
private E failureException;
|
||||||
|
|
||||||
|
public SynchronizationPoint(AbstractXMPPConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.connectionLock = connection.getConnectionLock();
|
||||||
|
this.condition = connection.getConnectionLock().newCondition();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
state = State.Initial;
|
||||||
|
failureException = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
||||||
|
NotConnectedException {
|
||||||
|
assert (state == State.Initial);
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
if (request != null) {
|
||||||
|
if (request instanceof Packet) {
|
||||||
|
connection.sendPacket((Packet) request);
|
||||||
|
}
|
||||||
|
else if (request instanceof PlainStreamElement){
|
||||||
|
connection.send((PlainStreamElement) request);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unsupported element type");
|
||||||
|
}
|
||||||
|
state = State.RequestSent;
|
||||||
|
}
|
||||||
|
waitForConditionOrTimeout();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
checkForResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendAndWaitForResponseOrThrow(PlainStreamElement request) throws E, NoResponseException,
|
||||||
|
NotConnectedException {
|
||||||
|
sendAndWaitForResponse(request);
|
||||||
|
switch (state) {
|
||||||
|
case Failure:
|
||||||
|
if (failureException != null) {
|
||||||
|
throw failureException;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Success, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E {
|
||||||
|
checkIfSuccessOrWait();
|
||||||
|
if (state == State.Failure) {
|
||||||
|
throw failureException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkIfSuccessOrWait() throws NoResponseException {
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
if (state == State.Success) {
|
||||||
|
// Return immediately
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
waitForConditionOrTimeout();
|
||||||
|
} finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
checkForResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportSuccess() {
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
state = State.Success;
|
||||||
|
condition.signal();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportFailure() {
|
||||||
|
reportFailure(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportFailure(E failureException) {
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
state = State.Failure;
|
||||||
|
this.failureException = failureException;
|
||||||
|
condition.signal();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasSuccessful() {
|
||||||
|
return state == State.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requestSent() {
|
||||||
|
return state == State.RequestSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForConditionOrTimeout() {
|
||||||
|
long remainingWait = TimeUnit.MILLISECONDS.toNanos(connection.getPacketReplyTimeout());
|
||||||
|
while (state == State.RequestSent || state == State.Initial) {
|
||||||
|
try {
|
||||||
|
remainingWait = condition.awaitNanos(
|
||||||
|
remainingWait);
|
||||||
|
if (remainingWait <= 0) {
|
||||||
|
state = State.NoResponse;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE, "was interrupted while waiting, this should not happen", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for a response and throw a {@link NoResponseException} if there was none.
|
||||||
|
* <p>
|
||||||
|
* The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
|
||||||
|
* </p>
|
||||||
|
* @throws NoResponseException
|
||||||
|
*/
|
||||||
|
private void checkForResponse() throws NoResponseException {
|
||||||
|
switch (state) {
|
||||||
|
case Initial:
|
||||||
|
case NoResponse:
|
||||||
|
case RequestSent:
|
||||||
|
throw new NoResponseException();
|
||||||
|
default:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
Initial,
|
||||||
|
RequestSent,
|
||||||
|
NoResponse,
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketFilter;
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -151,6 +153,13 @@ public interface XMPPConnection {
|
||||||
*/
|
*/
|
||||||
public void sendPacket(Packet packet) throws NotConnectedException;
|
public void sendPacket(Packet packet) throws NotConnectedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @throws NotConnectedException
|
||||||
|
*/
|
||||||
|
public void send(PlainStreamElement element) throws NotConnectedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the roster for the user.
|
* Returns the roster for the user.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -272,23 +281,6 @@ public interface XMPPConnection {
|
||||||
*/
|
*/
|
||||||
public void removePacketInterceptor(PacketInterceptor packetInterceptor);
|
public void removePacketInterceptor(PacketInterceptor packetInterceptor);
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the servers Entity Caps node
|
|
||||||
*
|
|
||||||
* XMPPConnection holds this information in order to avoid a dependency to
|
|
||||||
* smackx where EntityCapsManager lives from smack.
|
|
||||||
*
|
|
||||||
* @return the servers entity caps node
|
|
||||||
*/
|
|
||||||
public String getServiceCapsNode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the server supports roster versioning as defined in XEP-0237.
|
|
||||||
*
|
|
||||||
* @return true if the server supports roster versioning
|
|
||||||
*/
|
|
||||||
public boolean isRosterVersioningSupported();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current value of the reply timeout in milliseconds for request for this
|
* Returns the current value of the reply timeout in milliseconds for request for this
|
||||||
* XMPPConnection instance.
|
* XMPPConnection instance.
|
||||||
|
@ -354,13 +346,33 @@ public interface XMPPConnection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the roster will be loaded from the server when logging in. This
|
* Returns true if the roster will be loaded from the server when logging in. This
|
||||||
* is the common behaviour for clients but sometimes clients may want to differ this
|
* is the common behavior for clients but sometimes clients may want to differ this
|
||||||
* or just never do it if not interested in rosters.
|
* or just never do it if not interested in rosters.
|
||||||
*
|
*
|
||||||
* @return true if the roster will be loaded from the server when logging in.
|
* @return true if the roster will be loaded from the server when logging in.
|
||||||
|
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster-login">RFC 6121 2.2 - Retrieving the Roster on Login</a>
|
||||||
*/
|
*/
|
||||||
public boolean isRosterLoadedAtLogin();
|
public boolean isRosterLoadedAtLogin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the feature packet extensions for a given stream feature of the
|
||||||
|
* server, or <code>null</code> if the server doesn't support that feature.
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @param namespace
|
||||||
|
* @return a packet extensions of the feature or <code>null</code>
|
||||||
|
*/
|
||||||
|
public <F extends PacketExtension> F getFeature(String element, String namespace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the server supports the given stream feature.
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @param namespace
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean hasFeature(String element, String namespace);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a stanza and wait asynchronously for a response by using <code>replyFilter</code>.
|
* Send a stanza and wait asynchronously for a response by using <code>replyFilter</code>.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -464,4 +476,5 @@ public interface XMPPConnection {
|
||||||
* @return the timestamp in milliseconds
|
* @return the timestamp in milliseconds
|
||||||
*/
|
*/
|
||||||
public long getLastStanzaReceived();
|
public long getLastStanzaReceived();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,6 @@ public abstract class XMPPException extends Exception {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new XMPPException with a description of the exception and the
|
* Creates a new XMPPException with a description of the exception and the
|
||||||
* Throwable that was the root cause of the exception.
|
* Throwable that was the root cause of the exception.
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.compress.packet;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class Compress extends FullStreamElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "compress";
|
||||||
|
public static final String NAMESPACE = "http://jabber.org/protocol/compress";
|
||||||
|
|
||||||
|
public final String method;
|
||||||
|
|
||||||
|
public Compress(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
xml.element("method", method);
|
||||||
|
xml.closeElement(this);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Feature implements PacketExtension {
|
||||||
|
public static final String ELEMENT = "compression";
|
||||||
|
|
||||||
|
public final List<String> methods;
|
||||||
|
|
||||||
|
public Feature(List<String> methods) {
|
||||||
|
this.methods = methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMethods() {
|
||||||
|
return Collections.unmodifiableList(methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
for (String method : methods) {
|
||||||
|
xml.element("method", method);
|
||||||
|
}
|
||||||
|
xml.closeElement(this);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.compress.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||||
|
|
||||||
|
public class Compressed extends FullStreamElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "compressed";
|
||||||
|
public static final String NAMESPACE = Compress.NAMESPACE;
|
||||||
|
|
||||||
|
public static final Compressed INSTANCE = new Compressed();
|
||||||
|
|
||||||
|
private Compressed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toXML() {
|
||||||
|
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,6 +110,7 @@ public class Java7ZlibInputOutputStream extends XMPPInputOutputStream {
|
||||||
flushMethodInt = FULL_FLUSH_INT;
|
flushMethodInt = FULL_FLUSH_INT;
|
||||||
}
|
}
|
||||||
return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) {
|
return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) {
|
||||||
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
if (!supported) {
|
if (!supported) {
|
||||||
super.flush();
|
super.flush();
|
||||||
|
|
|
@ -176,10 +176,11 @@ public class ConsoleDebugger implements SmackDebugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void userHasLogged(String user) {
|
public void userHasLogged(String user) {
|
||||||
boolean isAnonymous = "".equals(XmppStringUtils.parseLocalpart(user));
|
String localpart = XmppStringUtils.parseLocalpart(user);
|
||||||
|
boolean isAnonymous = "".equals(localpart);
|
||||||
String title =
|
String title =
|
||||||
"User logged (" + connection.hashCode() + "): "
|
"User logged (" + connection.hashCode() + "): "
|
||||||
+ (isAnonymous ? "" : XmppStringUtils.parseBareAddress(user))
|
+ (isAnonymous ? "" : localpart)
|
||||||
+ "@"
|
+ "@"
|
||||||
+ connection.getServiceName()
|
+ connection.getServiceName()
|
||||||
+ ":"
|
+ ":"
|
||||||
|
|
|
@ -51,18 +51,6 @@ public class Bind extends IQ {
|
||||||
return jid;
|
return jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public XmlStringBuilder getChildElementXML() {
|
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
|
||||||
|
|
||||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
|
|
||||||
xml.optElement("resource", resource);
|
|
||||||
xml.optElement("jid", jid);
|
|
||||||
xml.closeElement(ELEMENT);
|
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bind newSet(String resource) {
|
public static Bind newSet(String resource) {
|
||||||
Bind bind = new Bind(resource, null);
|
Bind bind = new Bind(resource, null);
|
||||||
bind.setType(IQ.Type.set);
|
bind.setType(IQ.Type.set);
|
||||||
|
@ -72,4 +60,38 @@ public class Bind extends IQ {
|
||||||
public static Bind newResult(String jid) {
|
public static Bind newResult(String jid) {
|
||||||
return new Bind(null, jid);
|
return new Bind(null, jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder getChildElementXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder();
|
||||||
|
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
|
||||||
|
xml.optElement("resource", resource);
|
||||||
|
xml.optElement("jid", jid);
|
||||||
|
xml.closeElement(ELEMENT);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Feature implements PacketExtension {
|
||||||
|
|
||||||
|
public static final Feature INSTANCE = new Feature();
|
||||||
|
|
||||||
|
private Feature() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toXML() {
|
||||||
|
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,22 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.caps.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
import org.jivesoftware.smackx.caps.EntityCapsManager;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A XEP-0115 Entity Capabilities extension.
|
||||||
|
* <p>
|
||||||
|
* Note that this is currently in smack-core as it's a potential stream feature.
|
||||||
|
* TODO: In feature versions of Smack, it should be possible to register
|
||||||
|
* "providers" for stream features too, so that this class can be moved back to
|
||||||
|
* smack-extensions.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public class CapsExtension implements PacketExtension {
|
public class CapsExtension implements PacketExtension {
|
||||||
|
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
|
||||||
|
public static final String ELEMENT = "c";
|
||||||
|
|
||||||
private final String node, ver, hash;
|
private final String node, ver, hash;
|
||||||
|
|
||||||
|
@ -31,11 +40,11 @@ public class CapsExtension implements PacketExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getElementName() {
|
public String getElementName() {
|
||||||
return EntityCapsManager.ELEMENT;
|
return ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return EntityCapsManager.NAMESPACE;
|
return NAMESPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNode() {
|
public String getNode() {
|
||||||
|
@ -50,17 +59,24 @@ public class CapsExtension implements PacketExtension {
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
|
* <pre>
|
||||||
* <c xmlns='http://jabber.org/protocol/caps'
|
* <c xmlns='http://jabber.org/protocol/caps'
|
||||||
* hash='sha-1'
|
* hash='sha-1'
|
||||||
* node='http://code.google.com/p/exodus'
|
* node='http://code.google.com/p/exodus'
|
||||||
* ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>
|
* ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public CharSequence toXML() {
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
xml.attribute("hash", hash).attribute("node", node).attribute("ver", ver);
|
xml.attribute("hash", hash).attribute("node", node).attribute("ver", ver);
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CapsExtension from(Packet stanza) {
|
||||||
|
return stanza.getExtension(ELEMENT, NAMESPACE);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -24,14 +24,7 @@ package org.jivesoftware.smack.packet;
|
||||||
public interface Element {
|
public interface Element {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root element name.
|
* Returns the XML representation of this Element.
|
||||||
*
|
|
||||||
* @return the element name.
|
|
||||||
*/
|
|
||||||
public String getElementName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the XML representation of the PacketExtension.
|
|
||||||
*
|
*
|
||||||
* @return the packet extension as XML.
|
* @return the packet extension as XML.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for Stream elements. Everything that is not a stanza (RFC 6120 8.), ie. message,
|
||||||
|
* presence and iq, should sublcass this class instead of {@link Packet}.
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
public abstract class FullStreamElement extends PlainStreamElement implements PacketExtension {
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
*/
|
*/
|
||||||
public abstract class IQ extends Packet {
|
public abstract class IQ extends Packet {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "iq";
|
||||||
public static final String QUERY_ELEMENT = "query";
|
public static final String QUERY_ELEMENT = "query";
|
||||||
|
|
||||||
private Type type = Type.get;
|
private Type type = Type.get;
|
||||||
|
@ -78,7 +79,7 @@ public abstract class IQ extends Packet {
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML() {
|
public CharSequence toXML() {
|
||||||
XmlStringBuilder buf = new XmlStringBuilder();
|
XmlStringBuilder buf = new XmlStringBuilder();
|
||||||
buf.halfOpenElement("iq");
|
buf.halfOpenElement(ELEMENT);
|
||||||
addCommonAttributes(buf);
|
addCommonAttributes(buf);
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
buf.attribute("type", "get");
|
buf.attribute("type", "get");
|
||||||
|
@ -94,7 +95,7 @@ public abstract class IQ extends Packet {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
buf.append(error.toXML());
|
buf.append(error.toXML());
|
||||||
}
|
}
|
||||||
buf.closeElement("iq");
|
buf.closeElement(ELEMENT);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class Mechanisms implements PacketExtension {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "mechanisms";
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||||
|
|
||||||
|
public final List<String> mechanisms = new LinkedList<String>();
|
||||||
|
|
||||||
|
public Mechanisms(String mechanism) {
|
||||||
|
mechanisms.add(mechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mechanisms(Collection<String> mechanisms) {
|
||||||
|
this.mechanisms.addAll(mechanisms);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMechanisms() {
|
||||||
|
return Collections.unmodifiableList(mechanisms);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
for (String mechanism : mechanisms) {
|
||||||
|
xml.element("mechanism", mechanism);
|
||||||
|
}
|
||||||
|
xml.closeElement(this);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -53,6 +53,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
*/
|
*/
|
||||||
public class Message extends XmlLangStanza {
|
public class Message extends XmlLangStanza {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "message";
|
||||||
public static final String BODY = "body";
|
public static final String BODY = "body";
|
||||||
|
|
||||||
private Type type = Type.normal;
|
private Type type = Type.normal;
|
||||||
|
@ -394,7 +395,7 @@ public class Message extends XmlLangStanza {
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML() {
|
public XmlStringBuilder toXML() {
|
||||||
XmlStringBuilder buf = new XmlStringBuilder();
|
XmlStringBuilder buf = new XmlStringBuilder();
|
||||||
buf.halfOpenElement("message");
|
buf.halfOpenElement(ELEMENT);
|
||||||
buf.xmllangAttribute(language);
|
buf.xmllangAttribute(language);
|
||||||
addCommonAttributes(buf);
|
addCommonAttributes(buf);
|
||||||
if (type != Type.normal) {
|
if (type != Type.normal) {
|
||||||
|
@ -440,7 +441,7 @@ public class Message extends XmlLangStanza {
|
||||||
}
|
}
|
||||||
// Add packet extensions, if any are defined.
|
// Add packet extensions, if any are defined.
|
||||||
buf.append(getExtensionsXML());
|
buf.append(getExtensionsXML());
|
||||||
buf.closeElement("message");
|
buf.closeElement(ELEMENT);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to represent a XML element. This is similar to {@link PacketExtension}, but does not
|
||||||
|
* carry a namespace and is usually included as child element of an packet extension.
|
||||||
|
*/
|
||||||
|
public interface NamedElement extends Element {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root element name.
|
||||||
|
*
|
||||||
|
* @return the element name.
|
||||||
|
*/
|
||||||
|
public String getElementName();
|
||||||
|
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
*
|
*
|
||||||
* @author Matt Tucker
|
* @author Matt Tucker
|
||||||
*/
|
*/
|
||||||
public abstract class Packet {
|
public abstract class Packet extends TopLevelStreamElement {
|
||||||
|
|
||||||
public static final String TEXT = "text";
|
public static final String TEXT = "text";
|
||||||
public static final String ITEM = "item";
|
public static final String ITEM = "item";
|
||||||
|
@ -246,15 +246,6 @@ public abstract class Packet {
|
||||||
packetExtensions.remove(extension);
|
packetExtensions.remove(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the packet as XML. Every concrete extension of Packet must implement
|
|
||||||
* this method. In addition to writing out packet-specific data, every sub-class
|
|
||||||
* should also write out the error and the extensions data if they are defined.
|
|
||||||
*
|
|
||||||
* @return the XML format of the packet as a String.
|
|
||||||
*/
|
|
||||||
public abstract CharSequence toXML();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the extension sub-packets (including properties data) as an XML
|
* Returns the extension sub-packets (including properties data) as an XML
|
||||||
* String, or the Empty String if there are no packet extensions.
|
* String, or the Empty String if there are no packet extensions.
|
||||||
|
|
|
@ -28,7 +28,7 @@ package org.jivesoftware.smack.packet;
|
||||||
* @see org.jivesoftware.smack.provider.PacketExtensionProvider
|
* @see org.jivesoftware.smack.provider.PacketExtensionProvider
|
||||||
* @author Matt Tucker
|
* @author Matt Tucker
|
||||||
*/
|
*/
|
||||||
public interface PacketExtension extends Element {
|
public interface PacketExtension extends NamedElement {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root element XML namespace.
|
* Returns the root element XML namespace.
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plain stream elements, ie. everything that is <b>not a stanza</b> as defined
|
||||||
|
* RFC 6120 8. Stanzas are {@link Message}, {@link Presence} and {@link IQ}.
|
||||||
|
* Everything else should sublcass this class instead of {@link Packet}.
|
||||||
|
* <p>
|
||||||
|
* It is important to cleanly distinguish between stanzas and non-stanzas. For
|
||||||
|
* example plain stream elements don't count into the stanza count of XEP-198
|
||||||
|
* Stream Management.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
public abstract class PlainStreamElement extends TopLevelStreamElement {
|
||||||
|
|
||||||
|
}
|
|
@ -46,6 +46,8 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
*/
|
*/
|
||||||
public class Registration extends IQ {
|
public class Registration extends IQ {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = "jabber:iq:register";
|
||||||
|
|
||||||
private String instructions = null;
|
private String instructions = null;
|
||||||
private Map<String, String> attributes = null;
|
private Map<String, String> attributes = null;
|
||||||
|
|
||||||
|
@ -90,8 +92,8 @@ public class Registration extends IQ {
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder getChildElementXML() {
|
public XmlStringBuilder getChildElementXML() {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
XmlStringBuilder xml = new XmlStringBuilder();
|
||||||
xml.halfOpenElement("query");
|
xml.halfOpenElement(QUERY_ELEMENT);
|
||||||
xml.xmlnsAttribute("jabber:iq:register");
|
xml.xmlnsAttribute(NAMESPACE);
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
xml.optElement("instructions", instructions);
|
xml.optElement("instructions", instructions);
|
||||||
if (attributes != null && attributes.size() > 0) {
|
if (attributes != null && attributes.size() > 0) {
|
||||||
|
@ -102,7 +104,33 @@ public class Registration extends IQ {
|
||||||
}
|
}
|
||||||
// Add packet extensions, if any are defined.
|
// Add packet extensions, if any are defined.
|
||||||
xml.append(getExtensionsXML());
|
xml.append(getExtensionsXML());
|
||||||
xml.closeElement("query");
|
xml.closeElement(QUERY_ELEMENT);
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Feature implements PacketExtension {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "register";
|
||||||
|
public static final String NAMESPACE = "http://jabber.org/features/iq-register";
|
||||||
|
public static final Feature INSTANCE = new Registration.Feature();
|
||||||
|
|
||||||
|
private Feature() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class RosterVer implements PacketExtension {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "ver";
|
||||||
|
public static final String NAMESPACE = "urn:xmpp:features:rosterver";
|
||||||
|
|
||||||
|
public static final RosterVer INSTANCE = new RosterVer();
|
||||||
|
|
||||||
|
private RosterVer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,12 +32,40 @@ package org.jivesoftware.smack.packet;
|
||||||
*/
|
*/
|
||||||
public class Session extends IQ {
|
public class Session extends IQ {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "session";
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-session";
|
||||||
|
|
||||||
|
private static final String SESSION = '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||||
|
|
||||||
public Session() {
|
public Session() {
|
||||||
setType(IQ.Type.set);
|
setType(IQ.Type.set);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getChildElementXML() {
|
public String getChildElementXML() {
|
||||||
return "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>";
|
return SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Feature implements PacketExtension {
|
||||||
|
|
||||||
|
public static final Session.Feature INSTANCE = new Feature();
|
||||||
|
|
||||||
|
private Feature() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toXML() {
|
||||||
|
return SESSION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class StartTls extends FullStreamElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "starttls";
|
||||||
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||||
|
|
||||||
|
private final boolean required;
|
||||||
|
|
||||||
|
public StartTls() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StartTls(boolean required) {
|
||||||
|
this.required = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean required() {
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
xml.condEmptyElement(required, "required");
|
||||||
|
xml.closeElement(this);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class StreamOpen extends FullStreamElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "stream:stream";
|
||||||
|
public static final String NAMESPACE = "jabber:client";
|
||||||
|
public static final String VERSION = "1.0";
|
||||||
|
|
||||||
|
private final String service;
|
||||||
|
|
||||||
|
public StreamOpen(String service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.attribute("to", service);
|
||||||
|
xml.attribute("xmlns:stream", "http://etherx.jabber.org/streams");
|
||||||
|
xml.attribute("version", VERSION);
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A XMPP top level stream element. This is either a stanza ({@link Packet}) or
|
||||||
|
* just a plain stream element ({@link PlainStreamElement}).
|
||||||
|
*/
|
||||||
|
public abstract class TopLevelStreamElement implements Element {
|
||||||
|
|
||||||
|
}
|
|
@ -72,6 +72,10 @@ public class XMPPError {
|
||||||
private String message;
|
private String message;
|
||||||
private List<PacketExtension> applicationExtensions = null;
|
private List<PacketExtension> applicationExtensions = null;
|
||||||
|
|
||||||
|
public XMPPError(String condition) {
|
||||||
|
this(new Condition(condition));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new error with the specified condition inferring the type.
|
* Creates a new error with the specified condition inferring the type.
|
||||||
* If the Condition is predefined, client code should be like:
|
* If the Condition is predefined, client code should be like:
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
|
* Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
|
||||||
|
@ -111,13 +112,13 @@ public final class ProviderManager {
|
||||||
public static void addLoader(ProviderLoader loader) {
|
public static void addLoader(ProviderLoader loader) {
|
||||||
if (loader.getIQProviderInfo() != null) {
|
if (loader.getIQProviderInfo() != null) {
|
||||||
for (IQProviderInfo info : loader.getIQProviderInfo()) {
|
for (IQProviderInfo info : loader.getIQProviderInfo()) {
|
||||||
iqProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
iqProviders.put(getKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loader.getExtensionProviderInfo() != null) {
|
if (loader.getExtensionProviderInfo() != null) {
|
||||||
for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
|
for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
|
||||||
extensionProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
extensionProviders.put(getKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +144,7 @@ public final class ProviderManager {
|
||||||
* @return the IQ provider.
|
* @return the IQ provider.
|
||||||
*/
|
*/
|
||||||
public static Object getIQProvider(String elementName, String namespace) {
|
public static Object getIQProvider(String elementName, String namespace) {
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
return iqProviders.get(key);
|
return iqProviders.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +177,7 @@ public final class ProviderManager {
|
||||||
throw new IllegalArgumentException("Provider must be an IQProvider " +
|
throw new IllegalArgumentException("Provider must be an IQProvider " +
|
||||||
"or a Class instance sublcassing IQ.");
|
"or a Class instance sublcassing IQ.");
|
||||||
}
|
}
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
iqProviders.put(key, provider);
|
iqProviders.put(key, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +190,7 @@ public final class ProviderManager {
|
||||||
* @param namespace the XML namespace.
|
* @param namespace the XML namespace.
|
||||||
*/
|
*/
|
||||||
public static void removeIQProvider(String elementName, String namespace) {
|
public static void removeIQProvider(String elementName, String namespace) {
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
iqProviders.remove(key);
|
iqProviders.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +214,7 @@ public final class ProviderManager {
|
||||||
* @return the extenion provider.
|
* @return the extenion provider.
|
||||||
*/
|
*/
|
||||||
public static Object getExtensionProvider(String elementName, String namespace) {
|
public static Object getExtensionProvider(String elementName, String namespace) {
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
return extensionProviders.get(key);
|
return extensionProviders.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +234,7 @@ public final class ProviderManager {
|
||||||
throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
|
throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
|
||||||
"or a Class instance.");
|
"or a Class instance.");
|
||||||
}
|
}
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
extensionProviders.put(key, provider);
|
extensionProviders.put(key, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +247,7 @@ public final class ProviderManager {
|
||||||
* @param namespace the XML namespace.
|
* @param namespace the XML namespace.
|
||||||
*/
|
*/
|
||||||
public static void removeExtensionProvider(String elementName, String namespace) {
|
public static void removeExtensionProvider(String elementName, String namespace) {
|
||||||
String key = getProviderKey(elementName, namespace);
|
String key = getKey(elementName, namespace);
|
||||||
extensionProviders.remove(key);
|
extensionProviders.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,14 +262,7 @@ public final class ProviderManager {
|
||||||
return Collections.unmodifiableCollection(extensionProviders.values());
|
return Collections.unmodifiableCollection(extensionProviders.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String getKey(String elementName, String namespace) {
|
||||||
* Returns a String key for a given element name and namespace.
|
return XmppStringUtils.generateKey(elementName, namespace);
|
||||||
*
|
|
||||||
* @param elementName the element name.
|
|
||||||
* @param namespace the namespace.
|
|
||||||
* @return a unique key for the element name and namespace pair.
|
|
||||||
*/
|
|
||||||
private static String getProviderKey(String elementName, String namespace) {
|
|
||||||
return elementName + '#' + namespace;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
|
|
||||||
public class SASLErrorException extends XMPPException {
|
public class SASLErrorException extends XMPPException {
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ package org.jivesoftware.smack.sasl;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.AuthMechanism;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Response;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
authenticationText = "=";
|
authenticationText = "=";
|
||||||
}
|
}
|
||||||
// Send the authentication to the server
|
// Send the authentication to the server
|
||||||
connection.sendPacket(new AuthMechanism(getName(), authenticationText));
|
connection.send(new AuthMechanism(getName(), authenticationText));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +218,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the authentication to the server
|
// Send the authentication to the server
|
||||||
connection.sendPacket(responseStanza);
|
connection.send(responseStanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||||
|
|
|
@ -16,18 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl.packet;
|
package org.jivesoftware.smack.sasl.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
import org.jivesoftware.smack.sasl.SASLError;
|
import org.jivesoftware.smack.sasl.SASLError;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
public class SaslStanzas {
|
public class SaslStreamElements {
|
||||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiating SASL authentication by select a mechanism.
|
* Initiating SASL authentication by select a mechanism.
|
||||||
*/
|
*/
|
||||||
public static class AuthMechanism extends Packet {
|
public static class AuthMechanism extends PlainStreamElement {
|
||||||
public static final String ELEMENT = "auth";
|
public static final String ELEMENT = "auth";
|
||||||
|
|
||||||
private final String mechanism;
|
private final String mechanism;
|
||||||
|
@ -55,9 +55,9 @@ public class SaslStanzas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SASL challenge stanza.
|
* A SASL challenge stream element.
|
||||||
*/
|
*/
|
||||||
public static class Challenge extends Packet {
|
public static class Challenge extends PlainStreamElement {
|
||||||
public static final String ELEMENT = "challenge";
|
public static final String ELEMENT = "challenge";
|
||||||
|
|
||||||
private final String data;
|
private final String data;
|
||||||
|
@ -77,9 +77,9 @@ public class SaslStanzas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SASL response stanza.
|
* A SASL response stream element.
|
||||||
*/
|
*/
|
||||||
public static class Response extends Packet {
|
public static class Response extends PlainStreamElement {
|
||||||
public static final String ELEMENT = "response";
|
public static final String ELEMENT = "response";
|
||||||
|
|
||||||
private final String authenticationText;
|
private final String authenticationText;
|
||||||
|
@ -103,15 +103,15 @@ public class SaslStanzas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SASL success stanza.
|
* A SASL success stream element.
|
||||||
*/
|
*/
|
||||||
public static class Success extends Packet {
|
public static class Success extends PlainStreamElement {
|
||||||
public static final String ELEMENT = "success";
|
public static final String ELEMENT = "success";
|
||||||
|
|
||||||
final private String data;
|
final private String data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new SASL success stanza with optional additional data for the SASL layer
|
* Construct a new SASL success stream element with optional additional data for the SASL layer
|
||||||
* (RFC6120 6.3.10)
|
* (RFC6120 6.3.10)
|
||||||
*
|
*
|
||||||
* @param data additional data for the SASL layer or <code>null</code>
|
* @param data additional data for the SASL layer or <code>null</code>
|
||||||
|
@ -140,9 +140,9 @@ public class SaslStanzas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SASL failure stanza.
|
* A SASL failure stream element.
|
||||||
*/
|
*/
|
||||||
public static class SASLFailure extends Packet {
|
public static class SASLFailure extends PlainStreamElement {
|
||||||
public static final String ELEMENT = "failure";
|
public static final String ELEMENT = "failure";
|
||||||
|
|
||||||
private final SASLError saslError;
|
private final SASLError saslError;
|
|
@ -123,15 +123,20 @@ public class DNSUtil {
|
||||||
} else {
|
} else {
|
||||||
srvDomain = domain;
|
srvDomain = domain;
|
||||||
}
|
}
|
||||||
List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain);
|
try {
|
||||||
if (LOGGER.isLoggable(Level.FINE)) {
|
List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain);
|
||||||
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
|
if (LOGGER.isLoggable(Level.FINE)) {
|
||||||
for (SRVRecord r : srvRecords)
|
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
|
||||||
logMessage += " " + r;
|
for (SRVRecord r : srvRecords)
|
||||||
LOGGER.fine(logMessage);
|
logMessage += " " + r;
|
||||||
|
LOGGER.fine(logMessage);
|
||||||
|
}
|
||||||
|
List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
|
||||||
|
addresses.addAll(sortedRecords);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Exception while resolving SRV records for " + domain, e);
|
||||||
}
|
}
|
||||||
List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
|
|
||||||
addresses.addAll(sortedRecords);
|
|
||||||
|
|
||||||
// Step two: Add the hostname to the end of the list
|
// Step two: Add the hostname to the end of the list
|
||||||
addresses.add(new HostAddress(domain));
|
addresses.add(new HostAddress(domain));
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -30,6 +31,7 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.compress.packet.Compress;
|
||||||
import org.jivesoftware.smack.packet.Bind;
|
import org.jivesoftware.smack.packet.Bind;
|
||||||
import org.jivesoftware.smack.packet.DefaultPacketExtension;
|
import org.jivesoftware.smack.packet.DefaultPacketExtension;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
@ -39,12 +41,13 @@ import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Registration;
|
import org.jivesoftware.smack.packet.Registration;
|
||||||
import org.jivesoftware.smack.packet.RosterPacket;
|
import org.jivesoftware.smack.packet.RosterPacket;
|
||||||
|
import org.jivesoftware.smack.packet.StartTls;
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
import org.jivesoftware.smack.packet.XMPPError;
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
import org.jivesoftware.smack.provider.IQProvider;
|
import org.jivesoftware.smack.provider.IQProvider;
|
||||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||||
import org.jivesoftware.smack.provider.ProviderManager;
|
import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
@ -63,9 +66,7 @@ public class PacketParserUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
||||||
XmlPullParser parser = newXmppParser();
|
XmlPullParser parser = newXmppParser(reader);
|
||||||
parser.setInput(reader);
|
|
||||||
|
|
||||||
// Wind the parser forward to the first start tag
|
// Wind the parser forward to the first start tag
|
||||||
int event = parser.getEventType();
|
int event = parser.getEventType();
|
||||||
while (event != XmlPullParser.START_TAG) {
|
while (event != XmlPullParser.START_TAG) {
|
||||||
|
@ -116,20 +117,17 @@ public class PacketParserUtils {
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static Packet parseStanza(XmlPullParser parser, XMPPConnection connection) throws Exception {
|
public static Packet parseStanza(XmlPullParser parser, XMPPConnection connection) throws Exception {
|
||||||
final int eventType = parser.getEventType();
|
assert(parser.getEventType() == XmlPullParser.START_TAG);
|
||||||
if (eventType != XmlPullParser.START_TAG) {
|
|
||||||
throw new IllegalArgumentException("Parser not at start tag");
|
|
||||||
}
|
|
||||||
final String name = parser.getName();
|
final String name = parser.getName();
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "message":
|
case Message.ELEMENT:
|
||||||
return parseMessage(parser);
|
return parseMessage(parser);
|
||||||
case "iq":
|
case IQ.ELEMENT:
|
||||||
return parseIQ(parser, connection);
|
return parseIQ(parser, connection);
|
||||||
case "presence":
|
case Presence.ELEMENT:
|
||||||
return parsePresence(parser);
|
return parsePresence(parser);
|
||||||
default:
|
default:
|
||||||
return null;
|
throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +149,25 @@ public class PacketParserUtils {
|
||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
|
||||||
|
* FEATURE_PROCESS_NAMESPACES is enabled.
|
||||||
|
* <p>
|
||||||
|
* Note that not all XmlPullParser implementations will return a String on
|
||||||
|
* <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
|
||||||
|
* behavior when using the parser.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param reader
|
||||||
|
* @return A suitable XmlPullParser for XMPP parsing
|
||||||
|
* @throws XmlPullParserException
|
||||||
|
*/
|
||||||
|
public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException {
|
||||||
|
XmlPullParser parser = newXmppParser();
|
||||||
|
parser.setInput(reader);
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a message packet.
|
* Parses a message packet.
|
||||||
*
|
*
|
||||||
|
@ -525,8 +542,7 @@ public class PacketParserUtils {
|
||||||
else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
|
else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
|
||||||
iqPacket = parseRegistration(parser);
|
iqPacket = parseRegistration(parser);
|
||||||
}
|
}
|
||||||
else if (elementName.equals("bind") &&
|
else if (elementName.equals(Bind.ELEMENT) && namespace.equals(Bind.NAMESPACE)) {
|
||||||
namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
|
|
||||||
iqPacket = parseResourceBinding(parser);
|
iqPacket = parseResourceBinding(parser);
|
||||||
}
|
}
|
||||||
// Otherwise, see if there is a registered provider for
|
// Otherwise, see if there is a registered provider for
|
||||||
|
@ -688,25 +704,35 @@ public class PacketParserUtils {
|
||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
|
public static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
|
||||||
XmlPullParserException {
|
XmlPullParserException {
|
||||||
|
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||||
|
int initalDepth = parser.getDepth();
|
||||||
|
String name;
|
||||||
Bind bind = null;
|
Bind bind = null;
|
||||||
boolean done = false;
|
outerloop: while (true) {
|
||||||
while (!done) {
|
|
||||||
int eventType = parser.next();
|
int eventType = parser.next();
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
switch (eventType) {
|
||||||
if (parser.getName().equals("resource")) {
|
case XmlPullParser.START_TAG:
|
||||||
|
name = parser.getName();
|
||||||
|
switch (name) {
|
||||||
|
case "resource":
|
||||||
bind = Bind.newSet(parser.nextText());
|
bind = Bind.newSet(parser.nextText());
|
||||||
}
|
break;
|
||||||
else if (parser.getName().equals("jid")) {
|
case "jid":
|
||||||
bind = Bind.newResult(parser.nextText());
|
bind = Bind.newResult(parser.nextText());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
break;
|
||||||
if (parser.getName().equals(Bind.ELEMENT)) {
|
case XmlPullParser.END_TAG:
|
||||||
done = true;
|
name = parser.getName();
|
||||||
|
if (name.equals(Bind.ELEMENT) && parser.getDepth() == initalDepth) {
|
||||||
|
break outerloop;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert (parser.getEventType() == XmlPullParser.END_TAG);
|
||||||
return bind;
|
return bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,9 +741,11 @@ public class PacketParserUtils {
|
||||||
*
|
*
|
||||||
* @param parser the XML parser, positioned at the start of the mechanisms stanza.
|
* @param parser the XML parser, positioned at the start of the mechanisms stanza.
|
||||||
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
|
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
|
||||||
* @throws Exception if an exception occurs while parsing the stanza.
|
* @throws IOException
|
||||||
|
* @throws XmlPullParserException
|
||||||
*/
|
*/
|
||||||
public static Collection<String> parseMechanisms(XmlPullParser parser) throws Exception {
|
public static Collection<String> parseMechanisms(XmlPullParser parser)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
List<String> mechanisms = new ArrayList<String>();
|
List<String> mechanisms = new ArrayList<String>();
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
while (!done) {
|
while (!done) {
|
||||||
|
@ -739,32 +767,42 @@ public class PacketParserUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the available compression methods reported from the server.
|
* Parse the Compression Feature reported from the server.
|
||||||
*
|
*
|
||||||
* @param parser the XML parser, positioned at the start of the compression stanza.
|
* @param parser the XML parser, positioned at the start of the compression stanza.
|
||||||
* @return a collection of Stings with the methods included in the compression stanza.
|
* @return The CompressionFeature stream element
|
||||||
* @throws XmlPullParserException if an exception occurs while parsing the stanza.
|
* @throws XmlPullParserException if an exception occurs while parsing the stanza.
|
||||||
*/
|
*/
|
||||||
public static Collection<String> parseCompressionMethods(XmlPullParser parser)
|
public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
List<String> methods = new ArrayList<String>();
|
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||||
boolean done = false;
|
String name;
|
||||||
while (!done) {
|
final int initialDepth = parser.getDepth();
|
||||||
|
List<String> methods = new LinkedList<String>();
|
||||||
|
outerloop: while (true) {
|
||||||
int eventType = parser.next();
|
int eventType = parser.next();
|
||||||
|
switch (eventType) {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
case XmlPullParser.START_TAG:
|
||||||
String elementName = parser.getName();
|
name = parser.getName();
|
||||||
if (elementName.equals("method")) {
|
switch (name) {
|
||||||
|
case "method":
|
||||||
methods.add(parser.nextText());
|
methods.add(parser.nextText());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else if (eventType == XmlPullParser.END_TAG) {
|
case XmlPullParser.END_TAG:
|
||||||
if (parser.getName().equals("compression")) {
|
name = parser.getName();
|
||||||
done = true;
|
switch (name) {
|
||||||
|
case Compress.Feature.ELEMENT:
|
||||||
|
if (parser.getDepth() == initialDepth) {
|
||||||
|
break outerloop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return methods;
|
assert (parser.getEventType() == XmlPullParser.END_TAG);
|
||||||
|
assert (parser.getDepth() == initialDepth);
|
||||||
|
return new Compress.Feature(methods);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -840,21 +878,16 @@ public class PacketParserUtils {
|
||||||
* @throws Exception if an exception occurs while parsing the packet.
|
* @throws Exception if an exception occurs while parsing the packet.
|
||||||
*/
|
*/
|
||||||
public static XMPPError parseError(XmlPullParser parser) throws Exception {
|
public static XMPPError parseError(XmlPullParser parser) throws Exception {
|
||||||
final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
|
||||||
String type = null;
|
String type = null;
|
||||||
String message = null;
|
String message = null;
|
||||||
String condition = null;
|
String condition = null;
|
||||||
List<PacketExtension> extensions = new ArrayList<PacketExtension>();
|
List<PacketExtension> extensions = new ArrayList<PacketExtension>();
|
||||||
|
|
||||||
// Parse the error header
|
// Parse the error header
|
||||||
for (int i=0; i<parser.getAttributeCount(); i++) {
|
type = parser.getAttributeValue("", "type");
|
||||||
if (parser.getAttributeName(i).equals("type")) {
|
|
||||||
type = parser.getAttributeValue("", "type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boolean done = false;
|
|
||||||
// Parse the text and condition tags
|
// Parse the text and condition tags
|
||||||
while (!done) {
|
outerloop:
|
||||||
|
while (true) {
|
||||||
int eventType = parser.next();
|
int eventType = parser.next();
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
if (parser.getName().equals(Packet.TEXT)) {
|
if (parser.getName().equals(Packet.TEXT)) {
|
||||||
|
@ -864,7 +897,7 @@ public class PacketParserUtils {
|
||||||
// Condition tag, it can be xmpp error or an application defined error.
|
// Condition tag, it can be xmpp error or an application defined error.
|
||||||
String elementName = parser.getName();
|
String elementName = parser.getName();
|
||||||
String namespace = parser.getNamespace();
|
String namespace = parser.getNamespace();
|
||||||
if (errorNamespace.equals(namespace)) {
|
if (namespace.equals(XMPPError.NAMESPACE)) {
|
||||||
condition = elementName;
|
condition = elementName;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -874,7 +907,7 @@ public class PacketParserUtils {
|
||||||
}
|
}
|
||||||
else if (eventType == XmlPullParser.END_TAG) {
|
else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (parser.getName().equals("error")) {
|
if (parser.getName().equals("error")) {
|
||||||
done = true;
|
break outerloop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -942,6 +975,33 @@ public class PacketParserUtils {
|
||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static StartTls parseStartTlsFeature(XmlPullParser parser)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||||
|
assert (parser.getNamespace().equals(StartTls.NAMESPACE));
|
||||||
|
int initalDepth = parser.getDepth();
|
||||||
|
boolean required = false;
|
||||||
|
outerloop: while (true) {
|
||||||
|
int event = parser.next();
|
||||||
|
switch (event) {
|
||||||
|
case XmlPullParser.START_TAG:
|
||||||
|
String name = parser.getName();
|
||||||
|
switch (name) {
|
||||||
|
case "required":
|
||||||
|
required = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XmlPullParser.END_TAG:
|
||||||
|
if (parser.getDepth() == initalDepth) {
|
||||||
|
break outerloop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(parser.getEventType() == XmlPullParser.END_TAG);
|
||||||
|
return new StartTls(required);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getLanguageAttribute(XmlPullParser parser) {
|
private static String getLanguageAttribute(XmlPullParser parser) {
|
||||||
for (int i = 0; i < parser.getAttributeCount(); i++) {
|
for (int i = 0; i < parser.getAttributeCount(); i++) {
|
||||||
String attributeName = parser.getAttributeName(i);
|
String attributeName = parser.getAttributeName(i);
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
public class ParserUtils {
|
||||||
|
public static void assertAtStartTag(XmlPullParser parser) throws XmlPullParserException {
|
||||||
|
assert(parser.getEventType() == XmlPullParser.START_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertAtEndTag(XmlPullParser parser) throws XmlPullParserException {
|
||||||
|
assert(parser.getEventType() == XmlPullParser.END_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boolean value of an argument.
|
||||||
|
*
|
||||||
|
* @param parser
|
||||||
|
* @param name
|
||||||
|
* @return the boolean value or null of no argument of the given name exists
|
||||||
|
*/
|
||||||
|
public static Boolean getBooleanAttribute(XmlPullParser parser, String name) {
|
||||||
|
String valueString = parser.getAttributeValue("", name);
|
||||||
|
if (valueString == null)
|
||||||
|
return null;
|
||||||
|
valueString = valueString.toLowerCase(Locale.US);
|
||||||
|
if (valueString.equals("true") || valueString.equals("0")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getBooleanAttribute(XmlPullParser parser, String name,
|
||||||
|
boolean defaultValue) {
|
||||||
|
Boolean bool = getBooleanAttribute(parser, name);
|
||||||
|
if (bool == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getIntegerAttribute(XmlPullParser parser, String name) {
|
||||||
|
String valueString = parser.getAttributeValue("", name);
|
||||||
|
if (valueString == null)
|
||||||
|
return null;
|
||||||
|
return Integer.valueOf(valueString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getIntegerAttribute(XmlPullParser parser, String name, int defaultValue) {
|
||||||
|
Integer integer = getIntegerAttribute(parser, name);
|
||||||
|
if (integer == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return integer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getLongAttribute(XmlPullParser parser, String name) {
|
||||||
|
String valueString = parser.getAttributeValue("", name);
|
||||||
|
if (valueString == null)
|
||||||
|
return null;
|
||||||
|
return Long.valueOf(valueString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getLongAttribute(XmlPullParser parser, String name, long defaultValue) {
|
||||||
|
Long l = getLongAttribute(parser, name);
|
||||||
|
if (l == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
package org.jivesoftware.smack.util;
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.Element;
|
||||||
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
|
||||||
public class XmlStringBuilder implements Appendable, CharSequence {
|
public class XmlStringBuilder implements Appendable, CharSequence {
|
||||||
|
@ -33,7 +34,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
|
||||||
prelude(pe);
|
prelude(pe);
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmlStringBuilder(Element e) {
|
public XmlStringBuilder(NamedElement e) {
|
||||||
this();
|
this();
|
||||||
halfOpenElement(e.getElementName());
|
halfOpenElement(e.getElementName());
|
||||||
}
|
}
|
||||||
|
@ -101,7 +102,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmlStringBuilder closeElement(Element e) {
|
public XmlStringBuilder closeElement(NamedElement e) {
|
||||||
closeElement(e.getElementName());
|
closeElement(e.getElementName());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,10 @@ 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.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dummy implementation of {@link XMPPConnection}, intended to be used during
|
* A dummy implementation of {@link XMPPConnection}, intended to be used during
|
||||||
|
@ -53,7 +54,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
private String connectionID;
|
private String connectionID;
|
||||||
private Roster roster;
|
private Roster roster;
|
||||||
|
|
||||||
private final BlockingQueue<Packet> queue = new LinkedBlockingQueue<Packet>();
|
private final BlockingQueue<Element> queue = new LinkedBlockingQueue<Element>();
|
||||||
|
|
||||||
public DummyConnection() {
|
public DummyConnection() {
|
||||||
this(new ConnectionConfiguration("example.com"));
|
this(new ConnectionConfiguration("example.com"));
|
||||||
|
@ -178,6 +179,14 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(PlainStreamElement element) {
|
||||||
|
if (SmackConfiguration.DEBUG_ENABLED) {
|
||||||
|
System.out.println("[SEND]: " + element.toXML());
|
||||||
|
}
|
||||||
|
queue.add(element);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendPacketInternal(Packet packet) {
|
protected void sendPacketInternal(Packet packet) {
|
||||||
if (SmackConfiguration.DEBUG_ENABLED) {
|
if (SmackConfiguration.DEBUG_ENABLED) {
|
||||||
|
@ -204,7 +213,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public Packet getSentPacket() throws InterruptedException {
|
public Packet getSentPacket() throws InterruptedException {
|
||||||
return queue.poll();
|
return (Packet) queue.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -217,7 +226,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public Packet getSentPacket(int wait) throws InterruptedException {
|
public Packet getSentPacket(int wait) throws InterruptedException {
|
||||||
return queue.poll(wait, TimeUnit.SECONDS);
|
return (Packet) queue.poll(wait, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -69,8 +69,6 @@ public class RosterVersioningTest {
|
||||||
connection = new DummyConnection(conf);
|
connection = new DummyConnection(conf);
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
|
||||||
connection.setRosterVersioningSupported();
|
|
||||||
|
|
||||||
connection.login("rostertest", "secret");
|
connection.login("rostertest", "secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,7 @@ final public class TestUtils {
|
||||||
public static XmlPullParser getParser(Reader reader, String startTag) {
|
public static XmlPullParser getParser(Reader reader, String startTag) {
|
||||||
XmlPullParser parser;
|
XmlPullParser parser;
|
||||||
try {
|
try {
|
||||||
parser = PacketParserUtils.newXmppParser();
|
parser = PacketParserUtils.newXmppParser(reader);
|
||||||
parser.setInput(reader);
|
|
||||||
if (startTag == null) {
|
if (startTag == null) {
|
||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,7 +265,7 @@ public class Bytestream extends IQ {
|
||||||
*
|
*
|
||||||
* @author Alexander Wenckus
|
* @author Alexander Wenckus
|
||||||
*/
|
*/
|
||||||
public static class StreamHost implements Element {
|
public static class StreamHost implements NamedElement {
|
||||||
|
|
||||||
public static String ELEMENTNAME = "streamhost";
|
public static String ELEMENTNAME = "streamhost";
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ public class Bytestream extends IQ {
|
||||||
*
|
*
|
||||||
* @author Alexander Wenckus
|
* @author Alexander Wenckus
|
||||||
*/
|
*/
|
||||||
public static class StreamHostUsed implements Element {
|
public static class StreamHostUsed implements NamedElement {
|
||||||
|
|
||||||
public static String ELEMENTNAME = "streamhost-used";
|
public static String ELEMENTNAME = "streamhost-used";
|
||||||
|
|
||||||
|
@ -385,7 +385,7 @@ public class Bytestream extends IQ {
|
||||||
*
|
*
|
||||||
* @author Alexander Wenckus
|
* @author Alexander Wenckus
|
||||||
*/
|
*/
|
||||||
public static class Activate implements Element {
|
public static class Activate implements NamedElement {
|
||||||
|
|
||||||
public static String ELEMENTNAME = "activate";
|
public static String ELEMENTNAME = "activate";
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.jivesoftware.smack.PacketInterceptor;
|
||||||
import org.jivesoftware.smack.PacketListener;
|
import org.jivesoftware.smack.PacketListener;
|
||||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
|
import org.jivesoftware.smack.packet.CapsExtension;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
@ -38,7 +39,6 @@ import org.jivesoftware.smack.filter.PacketExtensionFilter;
|
||||||
import org.jivesoftware.smack.util.Cache;
|
import org.jivesoftware.smack.util.Cache;
|
||||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
|
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
|
||||||
import org.jivesoftware.smackx.caps.packet.CapsExtension;
|
|
||||||
import org.jivesoftware.smackx.disco.NodeInformationProvider;
|
import org.jivesoftware.smackx.disco.NodeInformationProvider;
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||||
|
@ -74,8 +74,8 @@ import java.security.NoSuchAlgorithmException;
|
||||||
public class EntityCapsManager extends Manager {
|
public class EntityCapsManager extends Manager {
|
||||||
private static final Logger LOGGER = Logger.getLogger(EntityCapsManager.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(EntityCapsManager.class.getName());
|
||||||
|
|
||||||
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
|
public static final String NAMESPACE = CapsExtension.NAMESPACE;
|
||||||
public static final String ELEMENT = "c";
|
public static final String ELEMENT = CapsExtension.ELEMENT;
|
||||||
|
|
||||||
private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
|
private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
|
||||||
private static String DEFAULT_ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
|
private static String DEFAULT_ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
|
||||||
|
@ -239,6 +239,17 @@ public class EntityCapsManager extends Manager {
|
||||||
CAPS_CACHE.clear();
|
CAPS_CACHE.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void addCapsExtensionInfo(String from, CapsExtension capsExtension) {
|
||||||
|
String hash = capsExtension.getHash().toLowerCase(Locale.US);
|
||||||
|
if (!SUPPORTED_HASHES.containsKey(hash))
|
||||||
|
return;
|
||||||
|
|
||||||
|
String node = capsExtension.getNode();
|
||||||
|
String ver = capsExtension.getVer();
|
||||||
|
|
||||||
|
JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash));
|
||||||
|
}
|
||||||
|
|
||||||
private final Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>();
|
private final Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>();
|
||||||
|
|
||||||
private final ServiceDiscoveryManager sdm;
|
private final ServiceDiscoveryManager sdm;
|
||||||
|
@ -258,6 +269,20 @@ public class EntityCapsManager extends Manager {
|
||||||
instances.put(connection, this);
|
instances.put(connection, this);
|
||||||
|
|
||||||
connection.addConnectionListener(new AbstractConnectionListener() {
|
connection.addConnectionListener(new AbstractConnectionListener() {
|
||||||
|
@Override
|
||||||
|
public void connected(XMPPConnection connection) {
|
||||||
|
// It's not clear when a server would report the caps stream
|
||||||
|
// feature, so we try to process it after we are connected and
|
||||||
|
// once after we are authenticated.
|
||||||
|
processCapsStreamFeatureIfAvailable(connection);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void authenticated(XMPPConnection connection) {
|
||||||
|
// It's not clear when a server would report the caps stream
|
||||||
|
// feature, so we try to process it after we are connected and
|
||||||
|
// once after we are authenticated.
|
||||||
|
processCapsStreamFeatureIfAvailable(connection);
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void connectionClosed() {
|
public void connectionClosed() {
|
||||||
presenceSend = false;
|
presenceSend = false;
|
||||||
|
@ -266,6 +291,16 @@ public class EntityCapsManager extends Manager {
|
||||||
public void connectionClosedOnError(Exception e) {
|
public void connectionClosedOnError(Exception e) {
|
||||||
presenceSend = false;
|
presenceSend = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processCapsStreamFeatureIfAvailable(XMPPConnection connection) {
|
||||||
|
CapsExtension capsExtension = connection.getFeature(
|
||||||
|
CapsExtension.ELEMENT, CapsExtension.NAMESPACE);
|
||||||
|
if (capsExtension == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String from = connection.getServiceName();
|
||||||
|
addCapsExtensionInfo(from, capsExtension);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// This calculates the local entity caps version
|
// This calculates the local entity caps version
|
||||||
|
@ -282,18 +317,9 @@ public class EntityCapsManager extends Manager {
|
||||||
if (!entityCapsEnabled())
|
if (!entityCapsEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CapsExtension ext = (CapsExtension) packet.getExtension(EntityCapsManager.ELEMENT,
|
CapsExtension capsExtension = CapsExtension.from(packet);
|
||||||
EntityCapsManager.NAMESPACE);
|
|
||||||
|
|
||||||
String hash = ext.getHash().toLowerCase(Locale.US);
|
|
||||||
if (!SUPPORTED_HASHES.containsKey(hash))
|
|
||||||
return;
|
|
||||||
|
|
||||||
String from = packet.getFrom();
|
String from = packet.getFrom();
|
||||||
String node = ext.getNode();
|
addCapsExtensionInfo(from, capsExtension);
|
||||||
String ver = ext.getVer();
|
|
||||||
|
|
||||||
JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, PRESENCES_WITH_CAPS);
|
}, PRESENCES_WITH_CAPS);
|
||||||
|
|
|
@ -19,10 +19,10 @@ package org.jivesoftware.smackx.caps.provider;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.packet.CapsExtension;
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||||
import org.jivesoftware.smackx.caps.EntityCapsManager;
|
import org.jivesoftware.smackx.caps.EntityCapsManager;
|
||||||
import org.jivesoftware.smackx.caps.packet.CapsExtension;
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import org.jxmpp.util.XmppDateTime;
|
||||||
*/
|
*/
|
||||||
public class DelayInformationProvider extends AbstractDelayInformationProvider {
|
public class DelayInformationProvider extends AbstractDelayInformationProvider {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
@Override
|
||||||
protected Date parseDate(String string) throws ParseException {
|
protected Date parseDate(String string) throws ParseException {
|
||||||
return XmppDateTime.parseXEP0082Date(string);
|
return XmppDateTime.parseXEP0082Date(string);
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.muc.packet;
|
package org.jivesoftware.smackx.muc.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public class Destroy implements Element {
|
public class Destroy implements NamedElement {
|
||||||
public static final String ELEMENT = "destroy";
|
public static final String ELEMENT = "destroy";
|
||||||
|
|
||||||
private String reason;
|
private String reason;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package org.jivesoftware.smackx.muc.packet;
|
package org.jivesoftware.smackx.muc.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
@ -119,7 +119,7 @@ public class MUCInitialPresence implements PacketExtension {
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public static class History implements Element {
|
public static class History implements NamedElement {
|
||||||
|
|
||||||
public static final String ELEMENT = "history";
|
public static final String ELEMENT = "history";
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.muc.packet;
|
package org.jivesoftware.smackx.muc.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
import org.jivesoftware.smackx.muc.MUCAffiliation;
|
import org.jivesoftware.smackx.muc.MUCAffiliation;
|
||||||
import org.jivesoftware.smackx.muc.MUCRole;
|
import org.jivesoftware.smackx.muc.MUCRole;
|
||||||
|
@ -27,7 +27,7 @@ import org.jivesoftware.smackx.muc.MUCRole;
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public class MUCItem implements Element {
|
public class MUCItem implements NamedElement {
|
||||||
public static final String ELEMENT = IQ.ITEM;
|
public static final String ELEMENT = IQ.ITEM;
|
||||||
|
|
||||||
private final MUCAffiliation affiliation;
|
private final MUCAffiliation affiliation;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package org.jivesoftware.smackx.muc.packet;
|
package org.jivesoftware.smackx.muc.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -218,7 +218,7 @@ public class MUCUser implements PacketExtension {
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public static class Invite implements Element {
|
public static class Invite implements NamedElement {
|
||||||
public static final String ELEMENT ="invite";
|
public static final String ELEMENT ="invite";
|
||||||
|
|
||||||
private String reason;
|
private String reason;
|
||||||
|
@ -304,7 +304,7 @@ public class MUCUser implements PacketExtension {
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public static class Decline implements Element {
|
public static class Decline implements NamedElement {
|
||||||
public static final String ELEMENT = "decline";
|
public static final String ELEMENT = "decline";
|
||||||
|
|
||||||
private String reason;
|
private String reason;
|
||||||
|
@ -390,7 +390,7 @@ public class MUCUser implements PacketExtension {
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public static class Status implements Element {
|
public static class Status implements NamedElement {
|
||||||
public static final String ELEMENT = "status";
|
public static final String ELEMENT = "status";
|
||||||
|
|
||||||
private static final Map<Integer, Status> statusMap = new HashMap<Integer, Status>(8);
|
private static final Map<Integer, Status> statusMap = new HashMap<Integer, Status>(8);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -27,6 +28,8 @@ import java.util.GregorianCalendar;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import javax.xml.parsers.FactoryConfigurationError;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jxmpp.util.XmppDateTime;
|
import org.jxmpp.util.XmppDateTime;
|
||||||
|
@ -35,14 +38,17 @@ import org.jivesoftware.smackx.delay.DelayInformationManager;
|
||||||
import org.jivesoftware.smackx.delay.packet.DelayInformation;
|
import org.jivesoftware.smackx.delay.packet.DelayInformation;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import com.jamesmurty.utils.XMLBuilder;
|
import com.jamesmurty.utils.XMLBuilder;
|
||||||
|
|
||||||
public class DelayInformationTest extends InitExtensions {
|
public class DelayInformationTest extends InitExtensions {
|
||||||
|
|
||||||
|
private static final Calendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25);
|
||||||
private static Properties outputProperties = new Properties();
|
private static Properties outputProperties = new Properties();
|
||||||
static {
|
static {
|
||||||
outputProperties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
|
outputProperties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||||
|
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -95,8 +101,6 @@ public class DelayInformationTest extends InitExtensions {
|
||||||
DelayInformationProvider p = new DelayInformationProvider();
|
DelayInformationProvider p = new DelayInformationProvider();
|
||||||
DelayInformation delayInfo;
|
DelayInformation delayInfo;
|
||||||
String control;
|
String control;
|
||||||
GregorianCalendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25);
|
|
||||||
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
||||||
|
|
||||||
// XEP-0082 date format
|
// XEP-0082 date format
|
||||||
control = XMLBuilder.create("delay")
|
control = XMLBuilder.create("delay")
|
||||||
|
@ -132,6 +136,14 @@ public class DelayInformationTest extends InitExtensions {
|
||||||
delayInfo = (DelayInformation) p.parseExtension(PacketParserUtils.getParserFor(control));
|
delayInfo = (DelayInformation) p.parseExtension(PacketParserUtils.getParserFor(control));
|
||||||
|
|
||||||
assertEquals(calendar.getTime(), delayInfo.getStamp());
|
assertEquals(calendar.getTime(), delayInfo.getStamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void legacyDateFormatsTest() throws FactoryConfigurationError, XmlPullParserException, IOException, Exception {
|
||||||
|
LegacyDelayInformationProvider p = new LegacyDelayInformationProvider();
|
||||||
|
DelayInformation delayInfo;
|
||||||
|
|
||||||
|
String control;
|
||||||
|
|
||||||
// XEP-0091 date format
|
// XEP-0091 date format
|
||||||
control = XMLBuilder.create("x")
|
control = XMLBuilder.create("x")
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class SMUtils {
|
||||||
|
|
||||||
|
private static long MASK_32_BIT = BigInteger.ONE.shiftLeft(32).subtract(BigInteger.ONE).longValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quoting XEP-198 4.:
|
||||||
|
* "In the unlikely case that the number of stanzas handled during a stream management session exceeds the number
|
||||||
|
* of digits that can be represented by the unsignedInt datatype as specified in XML Schema Part 2 [10]
|
||||||
|
* (i.e., 2^32), the value of 'h' SHALL be reset from 2^32-1 back to zero (rather than being incremented to 2^32)."
|
||||||
|
*
|
||||||
|
* @param height
|
||||||
|
* @return the incremented height
|
||||||
|
*/
|
||||||
|
public static long incrementHeight(long height) {
|
||||||
|
return ++height & MASK_32_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the delta of the last known stanza handled count and the new
|
||||||
|
* reported stanza handled count while considering that the new value may be
|
||||||
|
* wrapped after 2^32-1.
|
||||||
|
*
|
||||||
|
* @param reportedHandledCount
|
||||||
|
* @param lastKnownHandledCount
|
||||||
|
* @return the delta
|
||||||
|
*/
|
||||||
|
public static long calculateDelta(long reportedHandledCount, long lastKnownHandledCount) {
|
||||||
|
return (reportedHandledCount - lastKnownHandledCount) & MASK_32_BIT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
|
||||||
|
public abstract class StreamManagementException extends SmackException {
|
||||||
|
|
||||||
|
public StreamManagementException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamManagementException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 3767590115788821101L;
|
||||||
|
|
||||||
|
public static class StreamManagementNotEnabledException extends StreamManagementException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 2624821584352571307L;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StreamIdDoesNotMatchException extends StreamManagementException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1191073341336559621L;
|
||||||
|
|
||||||
|
public StreamIdDoesNotMatchException(String expected, String got) {
|
||||||
|
super("Stream IDs do not match. Expected '" + expected + "', but got '" + got + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,344 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class StreamManagement {
|
||||||
|
public static final String NAMESPACE = "urn:xmpp:sm:3";
|
||||||
|
|
||||||
|
public static class StreamManagementFeature implements PacketExtension {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "sm";
|
||||||
|
public static final StreamManagementFeature INSTANCE = new StreamManagementFeature();
|
||||||
|
|
||||||
|
private StreamManagementFeature() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class AbstractEnable extends FullStreamElement {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferred maximum resumption time in seconds (optional).
|
||||||
|
*/
|
||||||
|
protected int max = -1;
|
||||||
|
|
||||||
|
protected boolean resume = false;
|
||||||
|
|
||||||
|
protected void maybeAddResumeAttributeTo(XmlStringBuilder xml) {
|
||||||
|
if (resume) {
|
||||||
|
// XEP 198 never mentions the case where resume='false', it's either set to true or
|
||||||
|
// not set at all. We reflect this in this code part
|
||||||
|
xml.attribute("resume", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void maybeAddMaxAttributeTo(XmlStringBuilder xml) {
|
||||||
|
if (max > 0) {
|
||||||
|
xml.attribute("max", Integer.toString(max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isResumeSet() {
|
||||||
|
return resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the max resumption time in seconds.
|
||||||
|
* @return the max resumption time in seconds
|
||||||
|
*/
|
||||||
|
public int getMaxResumptionTime() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Enable extends AbstractEnable {
|
||||||
|
public static final String ELEMENT = "enable";
|
||||||
|
|
||||||
|
public static final Enable INSTANCE = new Enable();
|
||||||
|
|
||||||
|
private Enable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enable(boolean resume) {
|
||||||
|
this.resume = resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enable(boolean resume, int max) {
|
||||||
|
this(resume);
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
maybeAddResumeAttributeTo(xml);
|
||||||
|
maybeAddMaxAttributeTo(xml);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Stream Management 'enabled' element.
|
||||||
|
* <p>
|
||||||
|
* Here is a full example, all attributes besides 'xmlns' are optional.
|
||||||
|
* </p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* <enabled xmlns='urn:xmpp:sm:3'
|
||||||
|
* id='some-long-sm-id'
|
||||||
|
* location='[2001:41D0:1:A49b::1]:9222'
|
||||||
|
* resume='true'/>
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public static class Enabled extends AbstractEnable {
|
||||||
|
public static final String ELEMENT = "enabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream id ("SM-ID")
|
||||||
|
*/
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location where the server prefers reconnection.
|
||||||
|
*/
|
||||||
|
private final String location;
|
||||||
|
|
||||||
|
public Enabled(String id, boolean resume) {
|
||||||
|
this(id, resume, null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enabled(String id, boolean resume, String location, int max) {
|
||||||
|
this.id = id;
|
||||||
|
this.resume = resume;
|
||||||
|
this.location = location;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.optAttribute("id", id);
|
||||||
|
maybeAddResumeAttributeTo(xml);
|
||||||
|
xml.optAttribute("location", location);
|
||||||
|
maybeAddMaxAttributeTo(xml);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Failed extends FullStreamElement {
|
||||||
|
public static final String ELEMENT = "failed";
|
||||||
|
|
||||||
|
private XMPPError error;
|
||||||
|
|
||||||
|
public Failed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Failed(XMPPError error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XMPPError getXMPPError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
if (error != null) {
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
xml.append(error.toXML());
|
||||||
|
xml.closeElement(ELEMENT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
}
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class AbstractResume extends FullStreamElement {
|
||||||
|
|
||||||
|
private final long handledCount;
|
||||||
|
private final String previd;
|
||||||
|
|
||||||
|
public AbstractResume(long handledCount, String previd) {
|
||||||
|
this.handledCount = handledCount;
|
||||||
|
this.previd = previd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHandledCount() {
|
||||||
|
return handledCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrevId() {
|
||||||
|
return previd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.attribute("h", Long.toString(handledCount));
|
||||||
|
xml.attribute("previd", previd);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Resume extends AbstractResume {
|
||||||
|
public static final String ELEMENT = "resume";
|
||||||
|
|
||||||
|
public Resume(long handledCount, String previd) {
|
||||||
|
super(handledCount, previd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Resumed extends AbstractResume {
|
||||||
|
public static final String ELEMENT = "resumed";
|
||||||
|
|
||||||
|
public Resumed(long handledCount, String previd) {
|
||||||
|
super(handledCount, previd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AckAnswer extends FullStreamElement {
|
||||||
|
public static final String ELEMENT = "a";
|
||||||
|
|
||||||
|
private final long handledCount;
|
||||||
|
|
||||||
|
public AckAnswer(long handledCount) {
|
||||||
|
this.handledCount = handledCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHandledCount() {
|
||||||
|
return handledCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.attribute("h", Long.toString(handledCount));
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AckRequest extends FullStreamElement {
|
||||||
|
public static final String ELEMENT = "r";
|
||||||
|
public static final AckRequest INSTANCE = new AckRequest();
|
||||||
|
|
||||||
|
private AckRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence toXML() {
|
||||||
|
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
|
public class AfterXStanzas implements PacketFilter {
|
||||||
|
|
||||||
|
final int count;
|
||||||
|
int currentCount;
|
||||||
|
|
||||||
|
public AfterXStanzas(int count) {
|
||||||
|
this.count = count;
|
||||||
|
currentCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean accept(Packet packet) {
|
||||||
|
currentCount++;
|
||||||
|
if (currentCount == count) {
|
||||||
|
resetCounter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void resetCounter() {
|
||||||
|
currentCount = 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
|
public class ForEveryMessage implements PacketFilter {
|
||||||
|
|
||||||
|
public static final ForEveryMessage INSTANCE = new ForEveryMessage();
|
||||||
|
|
||||||
|
private ForEveryMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Packet packet) {
|
||||||
|
if (packet instanceof Message) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
|
public class ForEveryStanza implements PacketFilter {
|
||||||
|
|
||||||
|
public static final ForEveryStanza INSTANCE = new ForEveryStanza();
|
||||||
|
|
||||||
|
private ForEveryStanza() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Packet packet) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
|
public class ForMatchingPredicateOrAfterXStanzas implements PacketFilter {
|
||||||
|
|
||||||
|
private final PacketFilter predicate;
|
||||||
|
private final AfterXStanzas afterXStanzas;
|
||||||
|
|
||||||
|
public ForMatchingPredicateOrAfterXStanzas(PacketFilter predicate, int count) {
|
||||||
|
this.predicate = predicate;
|
||||||
|
this.afterXStanzas = new AfterXStanzas(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Packet packet) {
|
||||||
|
if (predicate.accept(packet)) {
|
||||||
|
afterXStanzas.resetCounter();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return afterXStanzas.accept(packet);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
public class OnceForThisStanza implements PacketFilter {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final XMPPTCPConnection connection;
|
||||||
|
|
||||||
|
public static void setup(XMPPTCPConnection connection, Packet packet) {
|
||||||
|
PacketFilter packetFilter = new OnceForThisStanza(connection, packet);
|
||||||
|
connection.addRequestAckPredicate(packetFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnceForThisStanza(XMPPTCPConnection connection, Packet packet) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.id = packet.getPacketID();
|
||||||
|
if (StringUtils.isNullOrEmpty(id)) {
|
||||||
|
throw new IllegalArgumentException("Stanza ID must be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Packet packet) {
|
||||||
|
String otherId = packet.getPacketID();
|
||||||
|
if (StringUtils.isNullOrEmpty(otherId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (id.equals(otherId)) {
|
||||||
|
connection.removeRequestAckPredicate(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
|
||||||
|
public class Predicate {
|
||||||
|
|
||||||
|
public static PacketFilter forMessagesOrAfter5Stanzas() {
|
||||||
|
return new ForMatchingPredicateOrAfterXStanzas(ForEveryMessage.INSTANCE, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AfterXStanzas after5Stanzas() {
|
||||||
|
return new AfterXStanzas(5);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.predicates;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
|
||||||
|
public class ShortcutPredicates implements PacketFilter {
|
||||||
|
|
||||||
|
private final Set<PacketFilter> predicates = new LinkedHashSet<PacketFilter>();
|
||||||
|
|
||||||
|
public ShortcutPredicates() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShortcutPredicates(Collection<? extends PacketFilter> predicates) {
|
||||||
|
this.predicates.addAll(predicates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addPredicate(PacketFilter predicate) {
|
||||||
|
return predicates.add(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removePredicate(PacketFilter prediacte) {
|
||||||
|
return predicates.remove(prediacte);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Packet packet) {
|
||||||
|
for (PacketFilter predicate : predicates) {
|
||||||
|
if (predicate.accept(packet)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
|
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.AckAnswer;
|
||||||
|
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Enabled;
|
||||||
|
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Failed;
|
||||||
|
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Resumed;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
public class ParseStreamManagement {
|
||||||
|
|
||||||
|
public static Enabled enabled(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
ParserUtils.assertAtStartTag(parser);
|
||||||
|
boolean resume = ParserUtils.getBooleanAttribute(parser, "resume", false);
|
||||||
|
String id = parser.getAttributeValue("", "id");
|
||||||
|
String location = parser.getAttributeValue("", "location");
|
||||||
|
int max = ParserUtils.getIntegerAttribute(parser, "max", -1);
|
||||||
|
parser.next();
|
||||||
|
ParserUtils.assertAtEndTag(parser);
|
||||||
|
return new Enabled(id, resume, location, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Failed failed(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
ParserUtils.assertAtStartTag(parser);
|
||||||
|
String name;
|
||||||
|
String condition = "unknown";
|
||||||
|
outerloop:
|
||||||
|
while(true) {
|
||||||
|
int event = parser.next();
|
||||||
|
switch (event) {
|
||||||
|
case XmlPullParser.START_TAG:
|
||||||
|
name = parser.getName();
|
||||||
|
String namespace = parser.getNamespace();
|
||||||
|
if (XMPPError.NAMESPACE.equals(namespace)) {
|
||||||
|
condition = name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case XmlPullParser.END_TAG:
|
||||||
|
name = parser.getName();
|
||||||
|
if (Failed.ELEMENT.equals(name)) {
|
||||||
|
break outerloop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParserUtils.assertAtEndTag(parser);
|
||||||
|
XMPPError error = new XMPPError(condition);
|
||||||
|
return new Failed(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Resumed resumed(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
ParserUtils.assertAtStartTag(parser);
|
||||||
|
long h = ParserUtils.getLongAttribute(parser, "h");
|
||||||
|
String previd = parser.getAttributeValue("", "previd");
|
||||||
|
parser.next();
|
||||||
|
ParserUtils.assertAtEndTag(parser);
|
||||||
|
return new Resumed(h, previd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AckAnswer ackAnswer(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
ParserUtils.assertAtStartTag(parser);
|
||||||
|
long h = ParserUtils.getLongAttribute(parser, "h");
|
||||||
|
parser.next();
|
||||||
|
ParserUtils.assertAtEndTag(parser);
|
||||||
|
return new AckAnswer(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -40,17 +40,20 @@ public class PacketWriterTest {
|
||||||
*
|
*
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws BrokenBarrierException
|
* @throws BrokenBarrierException
|
||||||
|
* @throws NotConnectedException
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("javadoc")
|
@SuppressWarnings("javadoc")
|
||||||
@Test
|
@Test
|
||||||
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException {
|
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException {
|
||||||
XMPPTCPConnection connection = new XMPPTCPConnection("foobar.com");
|
XMPPTCPConnection connection = new XMPPTCPConnection("foobar.com");
|
||||||
final PacketWriter pw = connection.new PacketWriter();
|
final PacketWriter pw = connection.new PacketWriter();
|
||||||
pw.setWriter(new BlockingStringWriter());
|
connection.packetWriter = pw;
|
||||||
pw.startup();
|
connection.packetReader = connection.new PacketReader();
|
||||||
|
connection.setWriter(new BlockingStringWriter());
|
||||||
|
pw.init();
|
||||||
|
|
||||||
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
||||||
pw.sendPacket(new Message());
|
pw.sendStreamElement(new Message());
|
||||||
}
|
}
|
||||||
|
|
||||||
final CyclicBarrier barrier = new CyclicBarrier(2);
|
final CyclicBarrier barrier = new CyclicBarrier(2);
|
||||||
|
@ -61,7 +64,7 @@ public class PacketWriterTest {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
barrier.await();
|
barrier.await();
|
||||||
pw.sendPacket(new Message());
|
pw.sendStreamElement(new Message());
|
||||||
// should only return after the pw was interrupted
|
// should only return after the pw was interrupted
|
||||||
if (!shutdown) {
|
if (!shutdown) {
|
||||||
prematureUnblocked = true;
|
prematureUnblocked = true;
|
||||||
|
@ -85,9 +88,9 @@ public class PacketWriterTest {
|
||||||
Thread.sleep(250);
|
Thread.sleep(250);
|
||||||
|
|
||||||
// Set to true for testing purposes, so that shutdown() won't wait packet writer
|
// Set to true for testing purposes, so that shutdown() won't wait packet writer
|
||||||
pw.shutdownDone.set(true);
|
pw.shutdownDone.reportSuccess();
|
||||||
// Shutdown the packetwriter
|
// Shutdown the packetwriter
|
||||||
pw.shutdown();
|
pw.shutdown(false);
|
||||||
shutdown = true;
|
shutdown = true;
|
||||||
barrier.await();
|
barrier.await();
|
||||||
if (prematureUnblocked) {
|
if (prematureUnblocked) {
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2014 Vyacheslav Blinov
|
||||||
|
*
|
||||||
|
* 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.tcp.sm.provider;
|
||||||
|
|
||||||
|
import com.jamesmurty.utils.XMLBuilder;
|
||||||
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
|
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class ParseStreamManagementTest {
|
||||||
|
private static final Properties outputProperties = initOutputProperties();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseEnabled() throws Exception {
|
||||||
|
String stanzaID = "zid615d9";
|
||||||
|
boolean resume = true;
|
||||||
|
String location = "test";
|
||||||
|
int max = 42;
|
||||||
|
|
||||||
|
String enabledStanza = XMLBuilder.create("enabled")
|
||||||
|
.a("xmlns", "urn:xmpp:sm:3")
|
||||||
|
.a("id", "zid615d9")
|
||||||
|
.a("resume", String.valueOf(resume))
|
||||||
|
.a("location", location)
|
||||||
|
.a("max", String.valueOf(max))
|
||||||
|
.asString(outputProperties);
|
||||||
|
|
||||||
|
StreamManagement.Enabled enabledPacket = ParseStreamManagement.enabled(
|
||||||
|
PacketParserUtils.getParserFor(enabledStanza));
|
||||||
|
|
||||||
|
assertThat(enabledPacket, is(notNullValue()));
|
||||||
|
assertThat(enabledPacket.getId(), equalTo(stanzaID));
|
||||||
|
assertThat(enabledPacket.getLocation(), equalTo(location));
|
||||||
|
assertThat(enabledPacket.isResumeSet(), equalTo(resume));
|
||||||
|
assertThat(enabledPacket.getMaxResumptionTime(), equalTo(max));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseEnabledInvariant() throws XmlPullParserException, IOException {
|
||||||
|
String enabledString = (new StreamManagement.Enabled("stream-id", false)).toXML().toString();
|
||||||
|
XmlPullParser parser = PacketParserUtils.getParserFor(enabledString);
|
||||||
|
StreamManagement.Enabled enabled = ParseStreamManagement.enabled(parser);
|
||||||
|
|
||||||
|
assertEquals(enabledString, enabled.toXML().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseFailed() throws Exception {
|
||||||
|
String failedStanza = XMLBuilder.create("failed")
|
||||||
|
.a("xmlns", "urn:xmpp:sm:3")
|
||||||
|
.asString(outputProperties);
|
||||||
|
|
||||||
|
StreamManagement.Failed failedPacket = ParseStreamManagement.failed(
|
||||||
|
PacketParserUtils.getParserFor(failedStanza));
|
||||||
|
|
||||||
|
assertThat(failedPacket, is(notNullValue()));
|
||||||
|
XMPPError error = failedPacket.getXMPPError();
|
||||||
|
|
||||||
|
assertThat(error, is(notNullValue()));
|
||||||
|
assertThat(error.getCondition(), equalTo("unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseFailedError() throws Exception {
|
||||||
|
String errorCondition = "failure";
|
||||||
|
|
||||||
|
String failedStanza = XMLBuilder.create("failed")
|
||||||
|
.a("xmlns", "urn:xmpp:sm:3")
|
||||||
|
.element(errorCondition, XMPPError.NAMESPACE)
|
||||||
|
.asString(outputProperties);
|
||||||
|
|
||||||
|
System.err.println(failedStanza);
|
||||||
|
|
||||||
|
StreamManagement.Failed failedPacket = ParseStreamManagement.failed(
|
||||||
|
PacketParserUtils.getParserFor(failedStanza));
|
||||||
|
|
||||||
|
assertThat(failedPacket, is(notNullValue()));
|
||||||
|
XMPPError error = failedPacket.getXMPPError();
|
||||||
|
|
||||||
|
assertThat(error, is(notNullValue()));
|
||||||
|
assertThat(error.getCondition(), equalTo(errorCondition));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseResumed() throws Exception {
|
||||||
|
long handledPackets = 42;
|
||||||
|
String previousID = "zid615d9";
|
||||||
|
|
||||||
|
String resumedStanza = XMLBuilder.create("resumed")
|
||||||
|
.a("xmlns", "urn:xmpp:sm:3")
|
||||||
|
.a("h", String.valueOf(handledPackets))
|
||||||
|
.a("previd", previousID)
|
||||||
|
.asString(outputProperties);
|
||||||
|
|
||||||
|
StreamManagement.Resumed resumedPacket = ParseStreamManagement.resumed(
|
||||||
|
PacketParserUtils.getParserFor(resumedStanza));
|
||||||
|
|
||||||
|
assertThat(resumedPacket, is(notNullValue()));
|
||||||
|
assertThat(resumedPacket.getHandledCount(), equalTo(handledPackets));
|
||||||
|
assertThat(resumedPacket.getPrevId(), equalTo(previousID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseAckAnswer() throws Exception {
|
||||||
|
long handledPackets = 42 + 42;
|
||||||
|
|
||||||
|
String ackStanza = XMLBuilder.create("a")
|
||||||
|
.a("xmlns", "urn:xmpp:sm:3")
|
||||||
|
.a("h", String.valueOf(handledPackets))
|
||||||
|
.asString(outputProperties);
|
||||||
|
|
||||||
|
StreamManagement.AckAnswer acknowledgementPacket = ParseStreamManagement.ackAnswer(
|
||||||
|
PacketParserUtils.getParserFor(ackStanza));
|
||||||
|
|
||||||
|
assertThat(acknowledgementPacket, is(notNullValue()));
|
||||||
|
assertThat(acknowledgementPacket.getHandledCount(), equalTo(handledPackets));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Properties initOutputProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue