mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-10-04 21:49:33 +02: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
|
@ -33,7 +33,7 @@ allprojects {
|
|||
// build, causing unnecessary rebuilds.
|
||||
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
|
||||
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
|
||||
jxmppVersion = "0.2.0"
|
||||
jxmppVersion = "0.3.0"
|
||||
}
|
||||
group = 'org.igniterealtime.smack'
|
||||
sourceCompatibility = 1.7
|
||||
|
|
|
@ -9,6 +9,14 @@ for many of the protocol extensions.
|
|||
This manual provides details about each of the "smackx" extensions, including
|
||||
what it is, how to use it, and some simple example code.
|
||||
|
||||
Currently supported XEPs of smack-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)
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
|
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 org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
@ -86,8 +85,6 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
|||
final String challengeData = parser.nextText();
|
||||
connection.getSASLAuthentication()
|
||||
.challengeReceived(challengeData);
|
||||
connection.processPacket(new Challenge(
|
||||
challengeData));
|
||||
} else if (parser.getName().equals("success")) {
|
||||
connection.send(ComposableBody.builder()
|
||||
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
||||
|
@ -100,14 +97,12 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
|||
.build());
|
||||
Success success = new Success(parser.nextText());
|
||||
connection.getSASLAuthentication().authenticated(success);
|
||||
connection.processPacket(success);
|
||||
} else if (parser.getName().equals("features")) {
|
||||
parseFeatures(parser);
|
||||
} else if (parser.getName().equals("failure")) {
|
||||
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
|
||||
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
|
||||
connection.getSASLAuthentication().authenticationFailed(failure);
|
||||
connection.processPacket(failure);
|
||||
}
|
||||
} else if (parser.getName().equals("error")) {
|
||||
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 {
|
||||
boolean done = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
connection.parseFeatures0(parser);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,9 +38,12 @@ import org.jivesoftware.smack.ConnectionCreationListener;
|
|||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.Roster;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Presence.Type;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.igniterealtime.jbosh.BOSHClient;
|
||||
import org.igniterealtime.jbosh.BOSHClientConfig;
|
||||
import org.igniterealtime.jbosh.BOSHClientConnEvent;
|
||||
|
@ -85,7 +88,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
// Some flags which provides some info about the current state.
|
||||
private boolean connected = false;
|
||||
private boolean authenticated = false;
|
||||
private boolean anonymous = false;
|
||||
private boolean isFirstInitialization = true;
|
||||
private boolean wasAuthenticated = false;
|
||||
private boolean done = false;
|
||||
|
@ -104,11 +106,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
*/
|
||||
protected String sessionID = null;
|
||||
|
||||
/**
|
||||
* The full JID of the authenticated user.
|
||||
*/
|
||||
private String user = null;
|
||||
|
||||
/**
|
||||
* Create a HTTP Binding connection to a XMPP server.
|
||||
*
|
||||
|
@ -220,10 +217,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
return user;
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return anonymous;
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
@ -250,6 +243,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
if (authenticated) {
|
||||
throw new AlreadyLoggedInException();
|
||||
}
|
||||
|
||||
// Wait with SASL auth until the SASL mechanisms have been received
|
||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||
|
||||
// Do partial version of nameprep on the username.
|
||||
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");
|
||||
}
|
||||
|
||||
String response = 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;
|
||||
}
|
||||
}
|
||||
bindResourceAndEstablishSession(resource);
|
||||
|
||||
// Indicate that we're now authenticated.
|
||||
authenticated = true;
|
||||
anonymous = false;
|
||||
|
||||
// Stores the autentication for future reconnection
|
||||
// Stores the authentication for future reconnection
|
||||
setLoginInfo(username, password, resource);
|
||||
|
||||
// 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));
|
||||
}
|
||||
afterSuccessfulLogin(false, false);
|
||||
}
|
||||
|
||||
public void loginAnonymously() throws XMPPException, SmackException, IOException {
|
||||
|
@ -309,6 +276,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
throw new AlreadyLoggedInException();
|
||||
}
|
||||
|
||||
// Wait with SASL auth until the SASL mechanisms have been received
|
||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||
|
||||
if (saslAuthentication.hasAnonymousAuthentication()) {
|
||||
saslAuthentication.authenticateAnonymously();
|
||||
}
|
||||
|
@ -317,38 +287,27 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
throw new SaslException("No anonymous SASL authentication mechanism available");
|
||||
}
|
||||
|
||||
String response = bindResourceAndEstablishSession(null);
|
||||
// Set the user value.
|
||||
this.user = response;
|
||||
// Update the serviceName with the one returned by the server
|
||||
setServiceName(response);
|
||||
bindResourceAndEstablishSession(null);
|
||||
|
||||
// Set presence to online.
|
||||
if (config.isSendPresence()) {
|
||||
sendPacket(new Presence(Presence.Type.available));
|
||||
afterSuccessfulLogin(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(PlainStreamElement element) throws NotConnectedException {
|
||||
if (done) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
|
||||
// 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();
|
||||
sendElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendPacketInternal(Packet packet) throws NotConnectedException {
|
||||
if (done) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
sendElement(packet);
|
||||
}
|
||||
|
||||
private void sendElement(Element element) {
|
||||
try {
|
||||
send(ComposableBody.builder().setPayloadXML(packet.toXML().toString()).build());
|
||||
send(ComposableBody.builder().setPayloadXML(element.toXML().toString()).build());
|
||||
}
|
||||
catch (BOSHException e) {
|
||||
LOGGER.log(Level.SEVERE, "BOSHException in sendPacketInternal", e);
|
||||
|
@ -524,19 +483,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
return super.getSASLAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverRequiresBinding() {
|
||||
super.serverRequiresBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverSupportsSession() {
|
||||
super.serverSupportsSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverSupportsAccountCreation() {
|
||||
super.serverSupportsAccountCreation();
|
||||
void parseFeatures0(XmlPullParser parser) throws Exception {
|
||||
parseFeatures(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
|||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
@ -28,27 +29,43 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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.Logger;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.compress.packet.Compress;
|
||||
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
|
||||
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.PacketIDFilter;
|
||||
import org.jivesoftware.smack.packet.Bind;
|
||||
import org.jivesoftware.smack.packet.CapsExtension;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Mechanisms;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
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.StartTls;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
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 {
|
||||
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 =
|
||||
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,7 +151,21 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -142,21 +182,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
private FromMode fromMode = FromMode.OMITTED;
|
||||
|
||||
/**
|
||||
* Stores whether the server supports rosterVersioning
|
||||
*/
|
||||
private boolean rosterVersioningSupported = false;
|
||||
|
||||
protected XMPPInputOutputStream compressionHandler;
|
||||
|
||||
/**
|
||||
|
@ -200,22 +230,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -227,6 +241,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected boolean wasAuthenticated = false;
|
||||
|
||||
private boolean anonymous = false;
|
||||
|
||||
/**
|
||||
* Create a new XMPPConnection to a XMPP server.
|
||||
*
|
||||
|
@ -267,14 +283,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
@Override
|
||||
public abstract boolean isAuthenticated();
|
||||
|
||||
@Override
|
||||
public abstract boolean isAnonymous();
|
||||
|
||||
@Override
|
||||
public abstract boolean isSecureConnection();
|
||||
|
||||
protected abstract void sendPacketInternal(Packet packet) throws NotConnectedException;
|
||||
|
||||
@Override
|
||||
public abstract void send(PlainStreamElement element) throws NotConnectedException;
|
||||
|
||||
@Override
|
||||
public abstract boolean isUsingCompression();
|
||||
|
||||
|
@ -292,22 +308,16 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
public void connect() throws SmackException, IOException, XMPPException {
|
||||
saslAuthentication.init();
|
||||
bindingRequired.set(false);
|
||||
sessionSupported = false;
|
||||
connectionException = null;
|
||||
saslFeatureReceived.init();
|
||||
lastFeaturesReceived.init();
|
||||
connectInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* block until the last features stanzas has been parsed and the features have been reported
|
||||
* 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).
|
||||
*
|
||||
* way of XMPP connection establishment. Implementations are required to perform an automatic
|
||||
* login if the previous connection state was logged (authenticated).
|
||||
*
|
||||
* @throws SmackException
|
||||
* @throws IOException
|
||||
* @throws XMPPException
|
||||
|
@ -383,44 +393,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
|
||||
|
||||
/**
|
||||
* Notification message saying that the server requires the client to bind a
|
||||
* resource to the stream.
|
||||
*/
|
||||
protected void serverRequiresBinding() {
|
||||
synchronized (bindingRequired) {
|
||||
bindingRequired.set(true);
|
||||
bindingRequired.notify();
|
||||
}
|
||||
}
|
||||
protected void bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
||||
IOException, SmackException {
|
||||
|
||||
/**
|
||||
* Notification message saying that the server supports sessions. When a server supports
|
||||
* sessions the client needs to send a Session packet after successfully binding a resource
|
||||
* for the session.
|
||||
*/
|
||||
protected void serverSupportsSession() {
|
||||
sessionSupported = true;
|
||||
}
|
||||
// Wait until either:
|
||||
// - the servers last features stanza has been parsed
|
||||
// - the timeout occurs
|
||||
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||
lastFeaturesReceived.checkIfSuccessOrWait();
|
||||
|
||||
protected String bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
||||
ResourceBindingNotOfferedException, NoResponseException, NotConnectedException {
|
||||
|
||||
synchronized (bindingRequired) {
|
||||
if (!bindingRequired.get()) {
|
||||
try {
|
||||
bindingRequired.wait(getPacketReplyTimeout());
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||
// 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.
|
||||
|
@ -435,9 +421,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
throw e;
|
||||
}
|
||||
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();
|
||||
packetCollector = createPacketCollector(new PacketIDFilter(session));
|
||||
try {
|
||||
|
@ -448,58 +435,59 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}
|
||||
packetCollector.nextResultOrThrow();
|
||||
}
|
||||
return userJID;
|
||||
}
|
||||
|
||||
protected void setConnectionException(Exception e) {
|
||||
connectionException = e;
|
||||
}
|
||||
protected void afterSuccessfulLogin(final boolean anonymous, final boolean resumed) throws NotConnectedException {
|
||||
// Indicate that we're now authenticated.
|
||||
this.authenticated = true;
|
||||
this.anonymous = anonymous;
|
||||
|
||||
protected void throwConnectionExceptionOrNoResponse() throws IOException, NoResponseException, SmackException {
|
||||
if (connectionException != null) {
|
||||
if (connectionException instanceof IOException) {
|
||||
throw (IOException) connectionException;
|
||||
} else if (connectionException instanceof SmackException) {
|
||||
throw (SmackException) connectionException;
|
||||
} else {
|
||||
throw new SmackException(connectionException);
|
||||
}
|
||||
} else {
|
||||
throw new NoResponseException();
|
||||
// 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();
|
||||
|
||||
// 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() && !resumed) {
|
||||
sendPacket(new Presence(Presence.Type.available));
|
||||
}
|
||||
}
|
||||
|
||||
protected Reader getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
protected Writer getWriter() {
|
||||
return writer;
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return anonymous;
|
||||
}
|
||||
|
||||
protected void setServiceName(String serviceName) {
|
||||
config.setServiceName(serviceName);
|
||||
}
|
||||
|
||||
|
||||
protected void setLoginInfo(String username, String password, String resource) {
|
||||
config.setLoginInfo(username, password, resource);
|
||||
}
|
||||
|
||||
protected void serverSupportsAccountCreation() {
|
||||
AccountManager.getInstance(this).setSupportsAccountCreation(true);
|
||||
}
|
||||
|
||||
protected void maybeResolveDns() throws Exception {
|
||||
config.maybeResolveDns();
|
||||
}
|
||||
|
||||
protected Lock getConnectionLock() {
|
||||
return connectionLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Packet packet) throws NotConnectedException {
|
||||
if (!isConnected()) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new NullPointerException("Packet is null.");
|
||||
throw new IllegalArgumentException("Packet must not be null");
|
||||
}
|
||||
switch (fromMode) {
|
||||
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
|
||||
public long getPacketReplyTimeout() {
|
||||
return packetReplyTimeout;
|
||||
|
@ -1055,6 +1014,112 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
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,
|
||||
new SmackExecutorThreadFactory(connectionCounterValue));
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.IQTypeFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
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.Presence;
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.RosterVer;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
@ -227,12 +227,15 @@ public class Roster {
|
|||
}
|
||||
|
||||
RosterPacket packet = new RosterPacket();
|
||||
if (rosterStore != null && connection.isRosterVersioningSupported()) {
|
||||
if (rosterStore != null && isRosterVersioningSupported()) {
|
||||
packet.setVersion(rosterStore.getRosterVersion());
|
||||
}
|
||||
PacketFilter filter = new IQReplyFilter(packet, connection);
|
||||
connection.addPacketListener(new RosterResultListener(), filter);
|
||||
connection.sendPacket(packet);
|
||||
connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
private boolean isRosterVersioningSupported() {
|
||||
return connection.hasFeature(RosterVer.ELEMENT, RosterVer.NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Intended to listen for a concrete roster result and deregisters
|
||||
* itself after a processed packet.
|
||||
* Handles roster reults as described in RFC 6121 2.1.4
|
||||
*/
|
||||
private class RosterResultListener implements PacketListener {
|
||||
|
||||
@Override
|
||||
public void processPacket(Packet packet) {
|
||||
connection.removePacketListener(this);
|
||||
|
||||
IQ result = (IQ)packet;
|
||||
if (!result.getType().equals(IQ.Type.result)) {
|
||||
LOGGER.severe("Roster result IQ not of type result. Packet: " + result.toXML());
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.fine("RosterResultListener received stanza");
|
||||
Collection<String> addedEntries = new ArrayList<String>();
|
||||
Collection<String> updatedEntries = 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.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.Mechanisms;
|
||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -128,13 +128,12 @@ public class SASLAuthentication {
|
|||
}
|
||||
|
||||
private final AbstractXMPPConnection connection;
|
||||
private Collection<String> serverMechanisms = new ArrayList<String>();
|
||||
private SASLMechanism currentMechanism = null;
|
||||
|
||||
/**
|
||||
* Boolean indicating if SASL negotiation has finished and was successful.
|
||||
*/
|
||||
private boolean saslNegotiated;
|
||||
private boolean authenticationSuccessful;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +243,7 @@ public class SASLAuthentication {
|
|||
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +282,7 @@ public class SASLAuthentication {
|
|||
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
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
|
||||
|
@ -355,7 +343,7 @@ public class SASLAuthentication {
|
|||
if (success.getData() != null) {
|
||||
challengeReceived(success.getData(), true);
|
||||
}
|
||||
saslNegotiated = true;
|
||||
authenticationSuccessful = true;
|
||||
// Wake up the thread that is waiting in the #authenticate method
|
||||
synchronized (this) {
|
||||
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
|
||||
* is used by the connection at the first login and then reused after the connection
|
||||
* is disconnected and then reconnected.
|
||||
*/
|
||||
protected void init() {
|
||||
saslNegotiated = false;
|
||||
authenticationSuccessful = false;
|
||||
saslException = null;
|
||||
}
|
||||
|
||||
|
@ -404,7 +396,7 @@ public class SASLAuthentication {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (serverMechanisms.contains(mechanismName)) {
|
||||
if (serverMechanisms().contains(mechanismName)) {
|
||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
||||
break;
|
||||
|
@ -412,4 +404,9 @@ public class SASLAuthentication {
|
|||
}
|
||||
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 {
|
||||
|
||||
/**
|
||||
|
@ -120,6 +131,10 @@ public class SmackException extends Exception {
|
|||
|
||||
public SecurityRequiredException() {
|
||||
}
|
||||
|
||||
public SecurityRequiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||