Apply builder pattern to ConnectionConfiguration

Introducing a clean split between the constant connection configuration
parameters, which are now all in ConnectionConfiguration and the dynamic
connection state (e.g. hostAddresses) which are now in
AbstractXMPPConnection.

Also removed all arguments of login() since the username, password,
resource and callback handler need now to be configured via
ConnectionConfiguration.

Also remove documentation/extensions/messageevents.md, as it's already
in documentation/legacy
This commit is contained in:
Florian Schmaus 2014-11-09 18:30:16 +01:00
parent 69f387b344
commit c81cd34561
24 changed files with 760 additions and 708 deletions

View File

@ -28,18 +28,21 @@ Connect and Disconnect
---------------------- ----------------------
``` ```
// Create the configuration for this new connection_ // Create the configuration for this new connection
ConnectionConfiguration config = new ConnectionConfiguration("jabber.org", 5222); XMPPTCPConnectionConfigurationBuilder configBuilder = XMPPTCPConnectionConfiguration.builder();
configBuilder.setUsernameAndPassword("username", "password");
configBuilder.setResource("SomeResource");
configBuilder.setServiceName("jabber.org");
AbstractXMPPConnection connection = new XMPPTCPConnection(config); AbstractXMPPConnection connection = new XMPPTCPConnection(configBuilder.build());
// Connect to the server_ // Connect to the server
connection.connect(); connection.connect();
// Log into the server_ // Log into the server
connection.login("username", "password", "SomeResource"); connection.login();
... ...
// Disconnect from the server_ // Disconnect from the server
connection.disconnect(); connection.disconnect();
``` ```

View File

@ -34,9 +34,7 @@ receive the roster entries.
In this example we can see how user1 sends his roster to user2. In this example we can see how user1 sends his roster to user2.
``` ```
// Connect to the server and log in XMPPConnection conn1 = …
conn1 = new XMPPConnection(host);
conn1.login(server_user1, pass1);
// Create a new roster exchange manager on conn1 // Create a new roster exchange manager on conn1
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
@ -64,9 +62,7 @@ the id of the user that will receive the roster entries.
In this example we can see how user1 sends his roster groups to user2. In this example we can see how user1 sends his roster groups to user2.
``` ```
// Connect to the server and log in XMPPConnection conn1 = …
conn1 = new XMPPConnection(host);
conn1.login(server_user1, pass1);
// Create a new roster exchange manager on conn1 // Create a new roster exchange manager on conn1
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
@ -95,9 +91,7 @@ the id of the user that will receive the roster entries.
In this example we can see how user1 sends a roster entry to user2. In this example we can see how user1 sends a roster entry to user2.
``` ```
// Connect to the server and log in XMPPConnection conn1 = …
conn1 = new XMPPConnection(host);
conn1.login(server_user1, pass1);
// Create a new roster exchange manager on conn1 // Create a new roster exchange manager on conn1
RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
@ -127,10 +121,9 @@ adds the received entries to his roster.
``` ```
// Connect to the server and log in the users // Connect to the server and log in the users
conn1 = new XMPPConnection(host); XMPPConnection conn1 = …
conn1.login(server_user1, pass1); XMPPConnection conn2 = …
conn2 = new XMPPConnection(host);
conn2.login(server_user2, pass2);
final Roster user2_roster = conn2.getRoster(); final Roster user2_roster = conn2.getRoster();
// Create a RosterExchangeManager that will help user2 to listen and accept // Create a RosterExchangeManager that will help user2 to listen and accept

View File

@ -44,13 +44,19 @@ The `XMPPTCPConnection` class is used to create a connection to an XMPP
server. Below are code examples for making a connection: server. Below are code examples for making a connection:
``` ```
// Create a connection to the jabber.org server._ // Create a connection to the jabber.org server.
XMPPConnection conn1 = **new** XMPPTCPConnection("jabber.org"); AbstractXMPPConnection conn1 = **new** XMPPTCPConnection("username", "password" "jabber.org");
conn1.connect(); conn1.connect();
// Create a connection to the jabber.org server on a specific port._ // Create a connection to the jabber.org server on a specific port.
ConnectionConfiguration config = new ConnectionConfiguration("jabber.org", 5222); XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
XMPPConnection conn2 = **new** XMPPTCPConnection(config); .setUsernameAndPassword("username", "password")
.setServiceName("jabber.org")
.setHost("earl.jabber.org")
.setPort("8222")
.build();
AbstractXMPPConnection conn2 = **new** XMPPTCPConnection(config);
conn2.connect(); conn2.connect();
``` ```
@ -60,10 +66,10 @@ ConnectionConfiguration class provides advanced control over the connection
created, such as the ability to disable or require encryption. See created, such as the ability to disable or require encryption. See
[XMPPConnection Management](connections.html) for full details. [XMPPConnection Management](connections.html) for full details.
Once you've created a connection, you should login using a username and Once you've created a connection, you should login with the
password with the `XMPPConnection.login(String username, String password)` `XMPPConnection.login()` method. Once you've logged in, you can being
method. Once you've logged in, you can being chatting with other users by chatting with other users by creating new `Chat` or `GroupChat`
creating new `Chat` or `GroupChat` objects. objects.
Working with the Roster Working with the Roster
---------------------- ----------------------

View File

@ -12,9 +12,9 @@ Smack Key Advantages
* Extremely simple to use, yet powerful API. Sending a text message to a user can be accomplished in only a few lines of code: * Extremely simple to use, yet powerful API. Sending a text message to a user can be accomplished in only a few lines of code:
```java ```java
AbstractXMPPConnection connection = new XMPPTCPConnection("jabber.org"); AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
connection.connect(); connection.connect();
connection.login("mtucker", "password"); connection.login();
Chat chat = ChatManager.getInstanceFor(connection) Chat chat = ChatManager.getInstanceFor(connection)
.createChat("jsmith@jivesoftware.com", new MessageListener() { .createChat("jsmith@jivesoftware.com", new MessageListener() {

View File

@ -22,7 +22,6 @@ import java.net.URISyntaxException;
import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.util.dns.HostAddress;
/** /**
* Configuration to use while establishing the connection to the XMPP server via * Configuration to use while establishing the connection to the XMPP server via
@ -33,60 +32,17 @@ import org.jivesoftware.smack.util.dns.HostAddress;
*/ */
public class BOSHConfiguration extends ConnectionConfiguration { public class BOSHConfiguration extends ConnectionConfiguration {
private boolean ssl; private final boolean https;
private String file; private final String file;
public BOSHConfiguration(String xmppDomain) { private BOSHConfiguration(BOSHConfigurationBuilder builder) {
super(xmppDomain, 7070); super(builder);
ssl = false; https = builder.https;
file = "/http-bind/"; if (builder.file.charAt(0) != '/') {
} file = '/' + builder.file;
} else {
public BOSHConfiguration(String xmppDomain, int port) { file = builder.file;
super(xmppDomain, port); }
ssl = false;
file = "/http-bind/";
}
/**
* Create a HTTP Binding configuration.
*
* @param https true if you want to use SSL
* (e.g. false for http://domain.lt:7070/http-bind).
* @param host the hostname or IP address of the connection manager
* (e.g. domain.lt for http://domain.lt:7070/http-bind).
* @param port the port of the connection manager
* (e.g. 7070 for http://domain.lt:7070/http-bind).
* @param filePath the file which is described by the URL
* (e.g. /http-bind for http://domain.lt:7070/http-bind).
* @param xmppDomain the XMPP service name
* (e.g. domain.lt for the user alice@domain.lt)
*/
public BOSHConfiguration(boolean https, String host, int port, String filePath, String xmppDomain) {
super(host, port, xmppDomain);
ssl = https;
file = (filePath != null ? filePath : "/");
}
/**
* Create a HTTP Binding configuration.
*
* @param https true if you want to use SSL
* (e.g. false for http://domain.lt:7070/http-bind).
* @param host the hostname or IP address of the connection manager
* (e.g. domain.lt for http://domain.lt:7070/http-bind).
* @param port the port of the connection manager
* (e.g. 7070 for http://domain.lt:7070/http-bind).
* @param filePath the file which is described by the URL
* (e.g. /http-bind for http://domain.lt:7070/http-bind).
* @param proxy the configuration of a proxy server.
* @param xmppDomain the XMPP service name
* (e.g. domain.lt for the user alice@domain.lt)
*/
public BOSHConfiguration(boolean https, String host, int port, String filePath, ProxyInfo proxy, String xmppDomain) {
super(host, port, xmppDomain, proxy);
ssl = https;
file = (filePath != null ? filePath : "/");
} }
public boolean isProxyEnabled() { public boolean isProxyEnabled() {
@ -105,24 +61,47 @@ public class BOSHConfiguration extends ConnectionConfiguration {
return (proxy != null ? proxy.getProxyPort() : 8080); return (proxy != null ? proxy.getProxyPort() : 8080);
} }
public boolean isUsingSSL() { public boolean isUsingHTTPS() {
return ssl; return https;
} }
public URI getURI() throws URISyntaxException { public URI getURI() throws URISyntaxException {
if (file.charAt(0) != '/') { return new URI((https ? "https://" : "http://") + this.host + ":" + this.port + file);
file = '/' + file; }
public static BOSHConfigurationBuilder builder() {
return new BOSHConfigurationBuilder();
}
public static class BOSHConfigurationBuilder extends ConnectionConfigurationBuilder<BOSHConfigurationBuilder, BOSHConfiguration> {
private boolean https;
private String file;
private BOSHConfigurationBuilder() {
} }
String host;
int port; public BOSHConfigurationBuilder setUseHttps(boolean useHttps) {
if (hostAddresses != null) { https = useHttps;
HostAddress hostAddress = hostAddresses.get(0); return this;
host = hostAddress.getFQDN(); }
port = hostAddress.getPort();
} else { public BOSHConfigurationBuilder useHttps() {
host = getServiceName(); return setUseHttps(true);
port = 80; }
public BOSHConfigurationBuilder setFile(String file) {
this.file = file;
return this;
}
@Override
public BOSHConfiguration build() {
return new BOSHConfiguration(this);
}
@Override
protected BOSHConfigurationBuilder getThis() {
return this;
} }
return new URI((ssl ? "https://" : "http://") + host + ":" + port + file);
} }
} }

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.io.PipedReader; import java.io.PipedReader;
import java.io.PipedWriter; import java.io.PipedWriter;
import java.io.Writer; import java.io.Writer;
import java.util.Locale;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -30,7 +29,6 @@ import javax.security.sasl.SaslException;
import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
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.SmackException.AlreadyLoggedInException;
import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
@ -106,6 +104,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
/** /**
* Create a HTTP Binding connection to a XMPP server. * Create a HTTP Binding connection to a XMPP server.
* *
* @param username the username to use.
* @param password the password to use.
* @param https true if you want to use SSL * @param https true if you want to use SSL
* (e.g. false for http://domain.lt:7070/http-bind). * (e.g. false for http://domain.lt:7070/http-bind).
* @param host the hostname or IP address of the connection manager * @param host the hostname or IP address of the connection manager
@ -117,9 +117,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
* @param xmppDomain the XMPP service name * @param xmppDomain the XMPP service name
* (e.g. domain.lt for the user alice@domain.lt) * (e.g. domain.lt for the user alice@domain.lt)
*/ */
public XMPPBOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) { public XMPPBOSHConnection(String username, String password, boolean https, String host, int port, String filePath, String xmppDomain) {
super(new BOSHConfiguration(https, host, port, filePath, xmppDomain)); this(BOSHConfiguration.builder().setUseHttps(https).setHost(host)
this.config = (BOSHConfiguration) getConfiguration(); .setPort(port).setFile(filePath).setServiceName(xmppDomain)
.setUsernameAndPassword(username, password).build());
} }
/** /**
@ -134,9 +135,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
@Override @Override
protected void connectInternal() throws SmackException { protected void connectInternal() throws SmackException {
if (connected) {
throw new IllegalStateException("Already connected to a server.");
}
done = false; done = false;
try { try {
// Ensure a clean starting state // Ensure a clean starting state
@ -224,18 +222,12 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
return false; return false;
} }
public void login(String username, String password, String resource) @Override
protected void loginNonAnonymously()
throws XMPPException, SmackException, IOException { throws XMPPException, SmackException, IOException {
if (!isConnected()) { String password = config.getPassword();
throw new NotConnectedException(); String resource = config.getResource();
} String username = config.getUsername();
if (authenticated) {
throw new AlreadyLoggedInException();
}
// Do partial version of nameprep on the username.
username = username.toLowerCase(Locale.US).trim();
if (saslAuthentication.hasNonAnonymousAuthentication()) { if (saslAuthentication.hasNonAnonymousAuthentication()) {
// Authenticate using SASL // Authenticate using SASL
if (password != null) { if (password != null) {
@ -249,19 +241,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
bindResourceAndEstablishSession(resource); bindResourceAndEstablishSession(resource);
// Stores the authentication for future reconnection afterSuccessfulLogin(false);
setLoginInfo(username, password, resource);
afterSuccessfulLogin(false, false);
} }
public void loginAnonymously() throws XMPPException, SmackException, IOException { @Override
if (!isConnected()) { protected void loginAnonymously() throws XMPPException, SmackException, IOException {
throw new NotConnectedException();
}
if (authenticated) {
throw new AlreadyLoggedInException();
}
// Wait with SASL auth until the SASL mechanisms have been received // Wait with SASL auth until the SASL mechanisms have been received
saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
@ -275,7 +259,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
bindResourceAndEstablishSession(null); bindResourceAndEstablishSession(null);
afterSuccessfulLogin(true, false); afterSuccessfulLogin(false);
} }
@Override @Override
@ -313,7 +297,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
*/ */
@Override @Override
protected void shutdown() { protected void shutdown() {
setWasAuthenticated(authenticated); setWasAuthenticated();
authID = null; authID = null;
sessionID = null; sessionID = null;
done = true; done = true;
@ -508,10 +492,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
else { else {
try { try {
if (wasAuthenticated) { if (wasAuthenticated) {
connection.login( connection.login();
config.getUsername(),
config.getPassword(),
config.getResource());
} }
for (ConnectionListener listener : getConnectionListeners()) { for (ConnectionListener listener : getConnectionListeners()) {
listener.reconnectionSuccessful(); listener.reconnectionSuccessful();

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smack;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
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.LinkedList;
@ -39,7 +40,10 @@ 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.ConnectionConfigurationBuilder;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SmackException.ConnectionException;
@ -66,7 +70,9 @@ import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.rosterstore.RosterStore; import org.jivesoftware.smack.rosterstore.RosterStore;
import org.jivesoftware.smack.util.Async; import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jxmpp.util.XmppStringUtils; import org.jxmpp.util.XmppStringUtils;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@ -255,8 +261,6 @@ 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.
* *
@ -272,6 +276,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@Override @Override
public String getServiceName() { public String getServiceName() {
if (serviceName != null) {
return serviceName;
}
return config.getServiceName(); return config.getServiceName();
} }
@ -312,6 +319,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @throws ConnectionException with detailed information about the failed connection. * @throws ConnectionException with detailed information about the failed connection.
*/ */
public void connect() throws SmackException, IOException, XMPPException { public void connect() throws SmackException, IOException, XMPPException {
throwAlreadyConnectedExceptionIfAppropriate();
saslAuthentication.init(); saslAuthentication.init();
saslFeatureReceived.init(); saslFeatureReceived.init();
lastFeaturesReceived.init(); lastFeaturesReceived.init();
@ -340,64 +348,31 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* Before logging in (i.e. authenticate) to the server the connection must be connected. * Before logging in (i.e. authenticate) to the server the connection must be connected.
* *
* It is possible to log in without sending an initial available presence by using * It is possible to log in without sending an initial available presence by using
* {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is * {@link ConnectionConfigurationBuilder#setSendPresence(boolean)}. If this connection is
* not interested in loading its roster upon login then use * not interested in loading its roster upon login then use
* {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. * {@link ConnectionConfigurationBuilder#setRosterLoadedAtLogin(boolean)}.
* Finally, if you want to not pass a password and instead use a more advanced mechanism * Finally, if you want to not pass a password and instead use a more advanced mechanism
* while using SASL then you may be interested in using * while using SASL then you may be interested in using
* {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. * {@link ConnectionConfigurationBuilder#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}.
* For more advanced login settings see {@link ConnectionConfiguration}. * For more advanced login settings see {@link ConnectionConfiguration}.
* *
* @param username the username.
* @param password the password or <tt>null</tt> if using a CallbackHandler.
* @throws XMPPException if an error occurs on the XMPP protocol level. * @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level. * @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException * @throws IOException
*/ */
public void login(String username, String password) throws XMPPException, SmackException, IOException { public void login() throws XMPPException, SmackException, IOException {
login(username, password, "Smack"); throwNotConnectedExceptionIfAppropriate();
throwAlreadyLoggedInExceptionIfAppropriate();
if (isAnonymous()) {
loginAnonymously();
} else {
loginNonAnonymously();
}
} }
/** protected abstract void loginNonAnonymously() throws XMPPException, SmackException, IOException;
* Logs in to the server using the strongest authentication mode supported by
* the server, then sets presence to available. If the server supports SASL authentication
* then the user will be authenticated using SASL if not Non-SASL authentication will
* be tried. If more than five seconds (default timeout) elapses in each step of the
* authentication process without a response from the server, or if an error occurs, a
* XMPPException will be thrown.<p>
*
* Before logging in (i.e. authenticate) to the server the connection must be connected.
*
* It is possible to log in without sending an initial available presence by using
* {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is
* not interested in loading its roster upon login then use
* {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}.
* Finally, if you want to not pass a password and instead use a more advanced mechanism
* while using SASL then you may be interested in using
* {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}.
* For more advanced login settings see {@link ConnectionConfiguration}.
*
* @param username the username.
* @param password the password or <tt>null</tt> if using a CallbackHandler.
* @param resource the resource.
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException
*/
public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, IOException;
/**
* Logs in to the server anonymously. Very few servers are configured to support anonymous
* authentication, so it's fairly likely logging in anonymously will fail. If anonymous login
* does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or
* "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server).
*
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
* @throws IOException
*/
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
protected abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
@Override @Override
public final boolean isConnected() { public final boolean isConnected() {
@ -437,7 +412,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
PacketCollector packetCollector = createPacketCollectorAndSend(new PacketIDFilter(bindResource), bindResource); PacketCollector packetCollector = createPacketCollectorAndSend(new PacketIDFilter(bindResource), bindResource);
Bind response = packetCollector.nextResultOrThrow(); Bind response = packetCollector.nextResultOrThrow();
user = response.getJid(); user = response.getJid();
setServiceName(XmppStringUtils.parseDomain(user)); serviceName = XmppStringUtils.parseDomain(user);
if (hasFeature(Session.ELEMENT, Session.NAMESPACE) && !getConfiguration().isLegacySessionDisabled()) { if (hasFeature(Session.ELEMENT, Session.NAMESPACE) && !getConfiguration().isLegacySessionDisabled()) {
Session session = new Session(); Session session = new Session();
@ -446,10 +421,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
} }
protected void afterSuccessfulLogin(final boolean anonymous, final boolean resumed) throws NotConnectedException { protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException {
// Indicate that we're now authenticated. // Indicate that we're now authenticated.
this.authenticated = true; this.authenticated = true;
this.anonymous = anonymous;
// If debugging is enabled, change the the debug window title to include the // If debugging is enabled, change the the debug window title to include the
// name we are now logged-in as. // name we are now logged-in as.
@ -476,33 +450,53 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@Override @Override
public boolean isAnonymous() { public boolean isAnonymous() {
return anonymous; return config.isAnonymous();
} }
protected void setServiceName(String serviceName) { private String serviceName;
config.setServiceName(serviceName);
}
protected void setLoginInfo(String username, String password, String resource) { protected List<HostAddress> hostAddresses;
config.setLoginInfo(username, password, resource);
}
protected void maybeResolveDns() throws Exception { protected void populateHostAddresses() throws Exception {
config.maybeResolveDns(); // N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName
if (config.host != null) {
hostAddresses = new ArrayList<HostAddress>(1);
HostAddress hostAddress;
hostAddress = new HostAddress(config.host, config.port);
hostAddresses.add(hostAddress);
} else {
hostAddresses = DNSUtil.resolveXMPPDomain(config.serviceName);
}
} }
protected Lock getConnectionLock() { protected Lock getConnectionLock() {
return connectionLock; return connectionLock;
} }
@Override protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException {
public void sendPacket(Packet packet) throws NotConnectedException {
if (!isConnected()) { if (!isConnected()) {
throw new NotConnectedException(); throw new NotConnectedException();
} }
}
protected void throwAlreadyConnectedExceptionIfAppropriate() throws AlreadyConnectedException {
if (isConnected()) {
throw new AlreadyConnectedException();
}
}
protected void throwAlreadyLoggedInExceptionIfAppropriate() throws AlreadyLoggedInException {
if (isAuthenticated()) {
throw new AlreadyLoggedInException();
}
}
@Override
public void sendPacket(Packet packet) throws NotConnectedException {
if (packet == null) { if (packet == null) {
throw new IllegalArgumentException("Packet must not be null"); throw new IllegalArgumentException("Packet must not be null");
} }
throwNotConnectedExceptionIfAppropriate();
switch (fromMode) { switch (fromMode) {
case OMITTED: case OMITTED:
packet.setFrom(null); packet.setFrom(null);
@ -897,9 +891,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* Sets whether the connection has already logged in the server. This method assures that the * Sets whether the connection has already logged in the server. This method assures that the
* {@link #wasAuthenticated} flag is never reset once it has ever been set. * {@link #wasAuthenticated} flag is never reset once it has ever been set.
* *
* @param authenticated true if the connection has already been authenticated.
*/ */
protected void setWasAuthenticated(boolean authenticated) { protected void setWasAuthenticated() {
// Never reset the flag if the connection has ever been authenticated // Never reset the flag if the connection has ever been authenticated
if (!wasAuthenticated) { if (!wasAuthenticated) {
wasAuthenticated = authenticated; wasAuthenticated = authenticated;
@ -1229,4 +1222,5 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected void reportStanzaReceived() { protected void reportStanzaReceived() {
this.lastStanzaReceived = System.currentTimeMillis(); this.lastStanzaReceived = System.currentTimeMillis();
} }
} }

View File

@ -17,33 +17,23 @@
package org.jivesoftware.smack; package org.jivesoftware.smack;
import java.util.Locale;
import org.jivesoftware.smack.packet.Session; import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.rosterstore.RosterStore; import org.jivesoftware.smack.rosterstore.RosterStore;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jxmpp.util.XmppStringUtils;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* Configuration to use while establishing the connection to the server. It is possible to * Configuration to use while establishing the connection to the server.
* configure the path to the trustore file that keeps the trusted CA root certificates and
* enable or disable all or some of the checkings done while verifying server certificates.<p>
*
* It is also possible to configure if TLS, SASL, and compression are used or not.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class ConnectionConfiguration implements Cloneable { public abstract class ConnectionConfiguration {
static { static {
// Ensure that Smack is initialized when ConnectionConfiguration is used, or otherwise e.g. // Ensure that Smack is initialized when ConnectionConfiguration is used, or otherwise e.g.
@ -56,168 +46,104 @@ public class ConnectionConfiguration implements Cloneable {
* of the server. However, there are some servers like google where host would be * of the server. However, there are some servers like google where host would be
* talk.google.com and the serviceName would be gmail.com. * talk.google.com and the serviceName would be gmail.com.
*/ */
private String serviceName; protected final String serviceName;
protected final String host;
protected final int port;
protected List<HostAddress> hostAddresses; private final String keystorePath;
private final String keystoreType;
private String keystorePath; private final String pkcs11Library;
private String keystoreType; private final SSLContext customSSLContext;
private String pkcs11Library;
private SSLContext customSSLContext;
private boolean compressionEnabled = false;
/** /**
* Used to get information from the user * Used to get information from the user
*/ */
private CallbackHandler callbackHandler; private final CallbackHandler callbackHandler;
private boolean debuggerEnabled = SmackConfiguration.DEBUG_ENABLED; private final boolean debuggerEnabled;
// Holds the socket factory that is used to generate the socket in the connection // Holds the socket factory that is used to generate the socket in the connection
private SocketFactory socketFactory; private final SocketFactory socketFactory;
// Holds the authentication information for future reconnections private final String username;
private String username; private final String password;
private String password; private final String resource;
private String resource; private final boolean sendPresence;
private boolean sendPresence = true; private final boolean rosterLoadedAtLogin;
private boolean rosterLoadedAtLogin = true; private final boolean legacySessionDisabled;
private boolean legacySessionDisabled = false; private final SecurityMode securityMode;
private boolean useDnsSrvRr = true;
private SecurityMode securityMode = SecurityMode.enabled;
/** /**
* *
*/ */
private String[] enabledSSLProtocols; private final String[] enabledSSLProtocols;
/** /**
* *
*/ */
private String[] enabledSSLCiphers; private final String[] enabledSSLCiphers;
private HostnameVerifier hostnameVerifier; private final HostnameVerifier hostnameVerifier;
/** /**
* Permanent store for the Roster, needed for roster versioning * Permanent store for the Roster, needed for roster versioning
*/ */
private RosterStore rosterStore; private final RosterStore rosterStore;
// Holds the proxy information (such as proxyhost, proxyport, username, password etc) // Holds the proxy information (such as proxyhost, proxyport, username, password etc)
protected ProxyInfo proxy; protected final ProxyInfo proxy;
/** protected ConnectionConfiguration(ConnectionConfigurationBuilder<?,?> builder) {
* Creates a new ConnectionConfiguration for the specified service name. if (builder.username != null) {
* A DNS SRV lookup will be performed to find out the actual host address // Do partial version of nameprep on the username.
* and port to use for the connection. username = builder.username.toLowerCase(Locale.US).trim();
* } else {
* @param serviceName the name of the service provided by an XMPP server. username = null;
*/ }
public ConnectionConfiguration(String serviceName) { password = builder.password;
init(serviceName, ProxyInfo.forDefaultProxy()); callbackHandler = builder.callbackHandler;
} if (callbackHandler == null && (password == null || username == null) && !builder.anonymous) {
throw new IllegalArgumentException(
/** "Must provide either a username and password, a callback handler or set the connection configuration anonymous");
* Creates a new ConnectionConfiguration for the specified service name
* with specified proxy.
* A DNS SRV lookup will be performed to find out the actual host address
* and port to use for the connection.
*
* @param serviceName the name of the service provided by an XMPP server.
* @param proxy the proxy through which XMPP is to be connected
*/
public ConnectionConfiguration(String serviceName,ProxyInfo proxy) {
init(serviceName, proxy);
}
/**
* Creates a new ConnectionConfiguration using the specified host, port and
* service name. This is useful for manually overriding the DNS SRV lookup
* process that's used with the {@link #ConnectionConfiguration(String)}
* constructor. For example, say that an XMPP server is running at localhost
* in an internal network on port 5222 but is configured to think that it's
* "example.com" for testing purposes. This constructor is necessary to connect
* to the server in that case since a DNS SRV lookup for example.com would not
* point to the local testing server.
*
* @param host the host where the XMPP server is running.
* @param port the port where the XMPP is listening.
* @param serviceName the name of the service provided by an XMPP server.
*/
public ConnectionConfiguration(String host, int port, String serviceName) {
initHostAddresses(host, port);
init(serviceName, ProxyInfo.forDefaultProxy());
}
/**
* Creates a new ConnectionConfiguration using the specified host, port and
* service name. This is useful for manually overriding the DNS SRV lookup
* process that's used with the {@link #ConnectionConfiguration(String)}
* constructor. For example, say that an XMPP server is running at localhost
* in an internal network on port 5222 but is configured to think that it's
* "example.com" for testing purposes. This constructor is necessary to connect
* to the server in that case since a DNS SRV lookup for example.com would not
* point to the local testing server.
*
* @param host the host where the XMPP server is running.
* @param port the port where the XMPP is listening.
* @param serviceName the name of the service provided by an XMPP server.
* @param proxy the proxy through which XMPP is to be connected
*/
public ConnectionConfiguration(String host, int port, String serviceName, ProxyInfo proxy) {
initHostAddresses(host, port);
init(serviceName, proxy);
}
/**
* Creates a new ConnectionConfiguration for a connection that will connect
* to the desired host and port.
*
* @param host the host where the XMPP server is running.
* @param port the port where the XMPP is listening.
*/
public ConnectionConfiguration(String host, int port) {
initHostAddresses(host, port);
init(host, ProxyInfo.forDefaultProxy());
}
/**
* Creates a new ConnectionConfiguration for a connection that will connect
* to the desired host and port with desired proxy.
*
* @param host the host where the XMPP server is running.
* @param port the port where the XMPP is listening.
* @param proxy the proxy through which XMPP is to be connected
*/
public ConnectionConfiguration(String host, int port, ProxyInfo proxy) {
initHostAddresses(host, port);
init(host, proxy);
}
protected void init(String serviceName, ProxyInfo proxy) {
if (StringUtils.isEmpty(serviceName)) {
throw new IllegalArgumentException("serviceName must not be the empty String");
} }
this.serviceName = serviceName;
this.proxy = proxy;
keystorePath = System.getProperty("javax.net.ssl.keyStore"); // Resource can be null, this means that the server must provide one
keystoreType = "jks"; resource = builder.resource;
pkcs11Library = "pkcs11.config";
serviceName = builder.serviceName;
//Setting the SocketFactory according to proxy supplied if (serviceName == null) {
socketFactory = proxy.getSocketFactory(); throw new IllegalArgumentException("Must provide XMPP service name");
}
host = builder.host;
port = builder.port;
proxy = builder.proxy;
if (proxy != null) {
if (builder.socketFactory != null) {
throw new IllegalArgumentException("Can not use proxy together with custom socket factory");
}
socketFactory = proxy.getSocketFactory();
} else {
socketFactory = builder.socketFactory;
}
securityMode = builder.securityMode;
keystoreType = builder.keystoreType;
keystorePath = builder.keystorePath;
pkcs11Library = builder.pkcs11Library;
customSSLContext = builder.customSSLContext;
enabledSSLProtocols = builder.enabledSSLProtocols;
enabledSSLCiphers = builder.enabledSSLCiphers;
hostnameVerifier = builder.hostnameVerifier;
sendPresence = builder.sendPresence;
rosterLoadedAtLogin = builder.rosterLoadedAtLogin;
legacySessionDisabled = builder.legacySessionDisabled;
rosterStore = builder.rosterStore;
debuggerEnabled = builder.debuggerEnabled;
} }
/** public boolean isAnonymous() {
* Sets the server name, also known as XMPP domain of the target server. return username == null && callbackHandler == null;
*
* @param serviceName the XMPP domain of the target server.
*/
void setServiceName(String serviceName) {
serviceName = XmppStringUtils.parseDomain(serviceName);
this.serviceName = serviceName;
} }
/** /**
@ -239,16 +165,6 @@ public class ConnectionConfiguration implements Cloneable {
return securityMode; return securityMode;
} }
/**
* Sets the TLS security mode used when making the connection. By default,
* the mode is {@link SecurityMode#enabled}.
*
* @param securityMode the security mode.
*/
public void setSecurityMode(SecurityMode securityMode) {
this.securityMode = securityMode;
}
/** /**
* Retuns the path to the keystore file. The key store file contains the * Retuns the path to the keystore file. The key store file contains the
* certificates that may be used to authenticate the client to the server, * certificates that may be used to authenticate the client to the server,
@ -260,17 +176,6 @@ public class ConnectionConfiguration implements Cloneable {
return keystorePath; return keystorePath;
} }
/**
* Sets the path to the keystore file. The key store file contains the
* certificates that may be used to authenticate the client to the server,
* in the event the server requests or requires it.
*
* @param keystorePath the path to the keystore file.
*/
public void setKeystorePath(String keystorePath) {
this.keystorePath = keystorePath;
}
/** /**
* Returns the keystore type, or <tt>null</tt> if it's not set. * Returns the keystore type, or <tt>null</tt> if it's not set.
* *
@ -280,16 +185,6 @@ public class ConnectionConfiguration implements Cloneable {
return keystoreType; return keystoreType;
} }
/**
* Sets the keystore type.
*
* @param keystoreType the keystore type.
*/
public void setKeystoreType(String keystoreType) {
this.keystoreType = keystoreType;
}
/** /**
* Returns the PKCS11 library file location, needed when the * Returns the PKCS11 library file location, needed when the
* Keystore type is PKCS11. * Keystore type is PKCS11.
@ -301,17 +196,7 @@ public class ConnectionConfiguration implements Cloneable {
} }
/** /**
* Sets the PKCS11 library file location, needed when the * Gets the custom SSLContext previously set with {@link ConnectionConfigurationBuilder#setCustomSSLContext(SSLContext)} for
* Keystore type is PKCS11
*
* @param pkcs11Library the path to the PKCS11 library file
*/
public void setPKCS11Library(String pkcs11Library) {
this.pkcs11Library = pkcs11Library;
}
/**
* Gets the custom SSLContext previously set with {@link #setCustomSSLContext(SSLContext)} for
* SSL sockets. This is null by default. * SSL sockets. This is null by default.
* *
* @return the custom SSLContext or null. * @return the custom SSLContext or null.
@ -320,28 +205,6 @@ public class ConnectionConfiguration implements Cloneable {
return this.customSSLContext; return this.customSSLContext;
} }
/**
* Sets a custom SSLContext for creating SSL sockets.
* <p>
* For more information on how to create a SSLContext see <a href=
* "http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#X509TrustManager"
* >Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager</a>
*
* @param context the custom SSLContext for new sockets
*/
public void setCustomSSLContext(SSLContext context) {
this.customSSLContext = context;
}
/**
* Set the enabled SSL/TLS protocols.
*
* @param enabledSSLProtocols
*/
public void setEnabledSSLProtocols(String[] enabledSSLProtocols) {
this.enabledSSLProtocols = enabledSSLProtocols;
}
/** /**
* Return the enabled SSL/TLS protocols. * Return the enabled SSL/TLS protocols.
* *
@ -351,15 +214,6 @@ public class ConnectionConfiguration implements Cloneable {
return enabledSSLProtocols; return enabledSSLProtocols;
} }
/**
* Set the enabled SSL/TLS ciphers.
*
* @param enabledSSLCiphers the enabled SSL/TLS ciphers
*/
public void setEnabledSSLCiphers(String[] enabledSSLCiphers) {
this.enabledSSLCiphers = enabledSSLCiphers;
}
/** /**
* Return the enabled SSL/TLS ciphers. * Return the enabled SSL/TLS ciphers.
* *
@ -369,16 +223,6 @@ public class ConnectionConfiguration implements Cloneable {
return enabledSSLCiphers; return enabledSSLCiphers;
} }
/**
* Set the HostnameVerifier used to verify the hostname of SSLSockets used by XMPP connections
* created with this ConnectionConfiguration.
*
* @param verifier
*/
public void setHostnameVerifier(HostnameVerifier verifier) {
hostnameVerifier = verifier;
}
/** /**
* Returns the configured HostnameVerifier of this ConnectionConfiguration or the Smack default * Returns the configured HostnameVerifier of this ConnectionConfiguration or the Smack default
* HostnameVerifier configured with * HostnameVerifier configured with
@ -392,30 +236,6 @@ public class ConnectionConfiguration implements Cloneable {
return SmackConfiguration.getDefaultHostnameVerifier(); return SmackConfiguration.getDefaultHostnameVerifier();
} }
/**
* Returns true if the connection is going to use stream compression. Stream compression
* will be requested after TLS was established (if TLS was enabled) and only if the server
* offered stream compression. With stream compression network traffic can be reduced
* up to 90%. By default compression is disabled.
*
* @return true if the connection is going to use stream compression.
*/
public boolean isCompressionEnabled() {
return compressionEnabled;
}
/**
* Sets if the connection is going to use stream compression. Stream compression
* will be requested after TLS was established (if TLS was enabled) and only if the server
* offered stream compression. With stream compression network traffic can be reduced
* up to 90%. By default compression is disabled.
*
* @param compressionEnabled if the connection is going to use stream compression.
*/
public void setCompressionEnabled(boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
}
/** /**
* Returns true if the new connection about to be establish is going to be debugged. By * Returns true if the new connection about to be establish is going to be debugged. By
* default the value of {@link SmackConfiguration#DEBUG_ENABLED} is used. * default the value of {@link SmackConfiguration#DEBUG_ENABLED} is used.
@ -426,38 +246,6 @@ public class ConnectionConfiguration implements Cloneable {
return debuggerEnabled; return debuggerEnabled;
} }
/**
* Sets if the new connection about to be establish is going to be debugged. By
* default the value of {@link SmackConfiguration#DEBUG_ENABLED} is used.
*
* @param debuggerEnabled if the new connection about to be establish is going to be debugged.
*/
public void setDebuggerEnabled(boolean debuggerEnabled) {
this.debuggerEnabled = debuggerEnabled;
}
/**
* Sets the socket factory used to create new xmppConnection sockets.
* This is useful when connecting through SOCKS5 proxies.
*
* @param socketFactory used to create new sockets.
*/
public void setSocketFactory(SocketFactory socketFactory) {
this.socketFactory = socketFactory;
}
/**
* Sets if an initial available presence will be sent to the server. By default
* an available presence will be sent to the server indicating that this presence
* is not online and available to receive messages. If you want to log in without
* being 'noticed' then pass a <tt>false</tt> value.
*
* @param sendPresence true if an initial available presence will be sent while logging in.
*/
public void setSendPresence(boolean sendPresence) {
this.sendPresence = sendPresence;
}
/** /**
* 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 behaviour for clients but sometimes clients may want to differ this
@ -469,17 +257,6 @@ public class ConnectionConfiguration implements Cloneable {
return rosterLoadedAtLogin; return rosterLoadedAtLogin;
} }
/**
* Sets 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
* or just never do it if not interested in rosters.
*
* @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in.
*/
public void setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) {
this.rosterLoadedAtLogin = rosterLoadedAtLogin;
}
/** /**
* Returns true if a {@link Session} will be requested on login if the server * Returns true if a {@link Session} will be requested on login if the server
* supports it. Although this was mandatory on RFC 3921, RFC 6120/6121 don't * supports it. Although this was mandatory on RFC 3921, RFC 6120/6121 don't
@ -491,17 +268,6 @@ public class ConnectionConfiguration implements Cloneable {
return legacySessionDisabled; return legacySessionDisabled;
} }
/**
* Sets if a {@link Session} will be requested on login if the server supports
* it. Although this was mandatory on RFC 3921, RFC 6120/6121 don't even
* mention this part of the protocol.
*
* @param legacySessionDisabled if a session has to be requested when logging in.
*/
public void setLegacySessionDisabled(boolean legacySessionDisabled) {
this.legacySessionDisabled = legacySessionDisabled;
}
/** /**
* Returns a CallbackHandler to obtain information, such as the password or * Returns a CallbackHandler to obtain information, such as the password or
* principal information during the SASL authentication. A CallbackHandler * principal information during the SASL authentication. A CallbackHandler
@ -515,19 +281,6 @@ public class ConnectionConfiguration implements Cloneable {
return callbackHandler; return callbackHandler;
} }
/**
* Sets a CallbackHandler to obtain information, such as the password or
* principal information during the SASL authentication. A CallbackHandler
* will be used <b>ONLY</b> if no password was specified during the login while
* using SASL authentication.
*
* @param callbackHandler to obtain information, such as the password or
* principal information during the SASL authentication.
*/
public void setCallbackHandler(CallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
}
/** /**
* Returns the socket factory used to create new xmppConnection sockets. * Returns the socket factory used to create new xmppConnection sockets.
* This is useful when connecting through SOCKS5 proxies. * This is useful when connecting through SOCKS5 proxies.
@ -538,17 +291,6 @@ public class ConnectionConfiguration implements Cloneable {
return this.socketFactory; return this.socketFactory;
} }
public List<HostAddress> getHostAddresses() {
return Collections.unmodifiableList(hostAddresses);
}
/**
* Set the permanent roster store
*/
public void setRosterStore(RosterStore store) {
rosterStore = store;
}
/** /**
* Get the permanent roster store * Get the permanent roster store
*/ */
@ -620,25 +362,319 @@ public class ConnectionConfiguration implements Cloneable {
return sendPresence; return sendPresence;
} }
void setLoginInfo(String username, String password, String resource) { /**
this.username = username; * Returns true if the connection is going to use stream compression. Stream compression
this.password = password; * will be requested after TLS was established (if TLS was enabled) and only if the server
this.resource = resource; * offered stream compression. With stream compression network traffic can be reduced
* up to 90%. By default compression is disabled.
*
* @return true if the connection is going to use stream compression.
*/
public boolean isCompressionEnabled() {
// Compression for non-TCP connections is always disabled
return false;
} }
void maybeResolveDns() throws Exception { /**
if (!useDnsSrvRr) return; * A builder for XMPP connection configurations.
hostAddresses = DNSUtil.resolveXMPPDomain(serviceName); * <p>
} * This is an abstract class that uses the builder design pattern and the "getThis() trick" to recover the type of
* the builder in a class hierarchies with a self-referential generic supertype. Otherwise chaining of build
* instructions from the superclasses followed by build instructions of a sublcass would not be possible, because
* the superclass build instructions would return the builder of the superclass and not the one of the subclass. You
* can read more about it a Angelika Langer's Generics FAQ, especially the entry <a
* href="http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206">What is the
* "getThis()" trick?</a>.
* </p>
*
* @param <B> the builder type parameter.
* @param <C> the resulting connection configuration type parameter.
*/
public static abstract class ConnectionConfigurationBuilder<B extends ConnectionConfigurationBuilder<B, C>, C extends ConnectionConfiguration> {
private SecurityMode securityMode = SecurityMode.enabled;
private String keystorePath = System.getProperty("javax.net.ssl.keyStore");
private String keystoreType = "jks";
private String pkcs11Library = "pkcs11.config";
private SSLContext customSSLContext;
private String[] enabledSSLProtocols;
private String[] enabledSSLCiphers;
private HostnameVerifier hostnameVerifier;
private String username;
private String password;
private boolean anonymous;
private String resource = "Smack";
private boolean sendPresence = true;
private boolean rosterLoadedAtLogin = true;
private boolean legacySessionDisabled = false;
private RosterStore rosterStore;
private ProxyInfo proxy;
private CallbackHandler callbackHandler;
private boolean debuggerEnabled = SmackConfiguration.DEBUG_ENABLED;
private SocketFactory socketFactory;
private String serviceName;
private String host;
private int port = 5222;
private void initHostAddresses(String host, int port) { protected ConnectionConfigurationBuilder() {
if (StringUtils.isEmpty(host)) {
throw new IllegalArgumentException("host must not be the empty String");
} }
hostAddresses = new ArrayList<HostAddress>(1);
HostAddress hostAddress; /**
hostAddress = new HostAddress(host, port); * Set the XMPP entities username and password.
hostAddresses.add(hostAddress); * <p>
useDnsSrvRr = false; * The username is the localpart of the entities JID, e.g. <code>localpart@example.org</code>. In order to
* create an anonymous connection, call {@link #makeAnonymous} instead.
* </p>
*
* @param username
* @param password
* @return a reference to this builder.
*/
public B setUsernameAndPassword(String username, String password) {
this.username = username;
this.password = password;
return getThis();
}
/**
* Create a configuration for a anonymous XMPP connection.
* <p>
* Anonyous connections don't provide a username or other authentification credentials like a password. Instead
* the XMPP server, if supporting anonymous connections, will assign a username to the client.
* </p>
*
* @return a reference to this builder.
*/
public B makeAnonymous() {
this.username = null;
this.password = null;
anonymous = true;
return getThis();
}
/**
* Set the service name of this XMPP service (i.e., the XMPP domain).
*
* @param serviceName the service name
* @return a reference to this builder.
*/
public B setServiceName(String serviceName) {
this.serviceName = serviceName;
return getThis();
}
/**
* Set the resource to use.
* <p>
* If <code>resource</code> is <code>null</code>, then the server will automatically create a resource for the
* client. Default resource is "Smack".
* </p>
*
* @param resource the resource to use.
* @return a reference to this builder.
*/
public B setResource(String resource) {
this.resource = resource;
return getThis();
}
public B setHost(String host) {
this.host = host;
return getThis();
}
public B setPort(int port) {
this.port = port;
return getThis();
}
/**
* Sets a CallbackHandler to obtain information, such as the password or
* principal information during the SASL authentication. A CallbackHandler
* will be used <b>ONLY</b> if no password was specified during the login while
* using SASL authentication.
*
* @param callbackHandler to obtain information, such as the password or
* principal information during the SASL authentication.
* @return a reference to this builder.
*/
public B setCallbackHandler(CallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
return getThis();
}
/**
* Sets the TLS security mode used when making the connection. By default,
* the mode is {@link SecurityMode#enabled}.
*
* @param securityMode the security mode.
* @return a reference to this builder.
*/
public B setSecurityMode(SecurityMode securityMode) {
this.securityMode = securityMode;
return getThis();
}
/**
* Sets the path to the keystore file. The key store file contains the
* certificates that may be used to authenticate the client to the server,
* in the event the server requests or requires it.
*
* @param keystorePath the path to the keystore file.
* @return a reference to this builder.
*/
public B setKeystorePath(String keystorePath) {
this.keystorePath = keystorePath;
return getThis();
}
/**
* Sets the keystore type.
*
* @param keystoreType the keystore type.
* @return a reference to this builder.
*/
public B setKeystoreType(String keystoreType) {
this.keystoreType = keystoreType;
return getThis();
}
/**
* Sets the PKCS11 library file location, needed when the
* Keystore type is PKCS11
*
* @param pkcs11Library the path to the PKCS11 library file.
* @return a reference to this builder.
*/
public B setPKCS11Library(String pkcs11Library) {
this.pkcs11Library = pkcs11Library;
return getThis();
}
/**
* Sets a custom SSLContext for creating SSL sockets.
* <p>
* For more information on how to create a SSLContext see <a href=
* "http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#X509TrustManager"
* >Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager</a>
*
* @param context the custom SSLContext for new sockets.
* @return a reference to this builder.
*/
public B setCustomSSLContext(SSLContext context) {
this.customSSLContext = context;
return getThis();
}
/**
* Set the enabled SSL/TLS protocols.
*
* @param enabledSSLProtocols
* @return a reference to this builder.
*/
public B setEnabledSSLProtocols(String[] enabledSSLProtocols) {
this.enabledSSLProtocols = enabledSSLProtocols;
return getThis();
}
/**
* Set the enabled SSL/TLS ciphers.
*
* @param enabledSSLCiphers the enabled SSL/TLS ciphers
* @return a reference to this builder.
*/
public B setEnabledSSLCiphers(String[] enabledSSLCiphers) {
this.enabledSSLCiphers = enabledSSLCiphers;
return getThis();
}
/**
* Set the HostnameVerifier used to verify the hostname of SSLSockets used by XMPP connections
* created with this ConnectionConfiguration.
*
* @param verifier
* @return a reference to this builder.
*/
public B setHostnameVerifier(HostnameVerifier verifier) {
hostnameVerifier = verifier;
return getThis();
}
/**
* Sets if a {@link Session} will be requested on login if the server supports
* it. Although this was mandatory on RFC 3921, RFC 6120/6121 don't even
* mention this part of the protocol.
*
* @param legacySessionDisabled if a session has to be requested when logging in.
* @return a reference to this builder.
*/
public B setLegacySessionDisabled(boolean legacySessionDisabled) {
this.legacySessionDisabled = legacySessionDisabled;
return getThis();
}
/**
* Sets 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
* or just never do it if not interested in rosters.
*
* @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in.
* @return a reference to this builder.
*/
public B setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) {
this.rosterLoadedAtLogin = rosterLoadedAtLogin;
return getThis();
}
/**
* Sets if an initial available presence will be sent to the server. By default
* an available presence will be sent to the server indicating that this presence
* is not online and available to receive messages. If you want to log in without
* being 'noticed' then pass a <tt>false</tt> value.
*
* @param sendPresence true if an initial available presence will be sent while logging in.
* @return a reference to this builder.
*/
public B setSendPresence(boolean sendPresence) {
this.sendPresence = sendPresence;
return getThis();
}
/**
* Set the permanent roster store.
*
* @return a reference to this builder.
*/
public B setRosterStore(RosterStore store) {
rosterStore = store;
return getThis();
}
/**
* Sets if the new connection about to be establish is going to be debugged. By
* default the value of {@link SmackConfiguration#DEBUG_ENABLED} is used.
*
* @param debuggerEnabled if the new connection about to be establish is going to be debugged.
* @return a reference to this builder.
*/
public B setDebuggerEnabled(boolean debuggerEnabled) {
this.debuggerEnabled = debuggerEnabled;
return getThis();
}
/**
* Sets the socket factory used to create new xmppConnection sockets.
* This is useful when connecting through SOCKS5 proxies.
*
* @param socketFactory used to create new sockets.
* @return a reference to this builder.
*/
public B setSocketFactory(SocketFactory socketFactory) {
this.socketFactory = socketFactory;
return getThis();
}
public abstract C build();
protected abstract B getThis();
} }
} }

View File

@ -28,6 +28,7 @@ import java.util.Set;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder;
import org.jivesoftware.smack.compression.XMPPInputOutputStream; import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory; import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory;
import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.debugger.SmackDebugger;
@ -267,7 +268,7 @@ public final class SmackConfiguration {
* Set the default HostnameVerifier that will be used by XMPP connections to verify the hostname * Set the default HostnameVerifier that will be used by XMPP connections to verify the hostname
* of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a * of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a
* HostnameVerifier in their ConnecitonConfiguration with * HostnameVerifier in their ConnecitonConfiguration with
* {@link ConnectionConfiguration#setHostnameVerifier(HostnameVerifier)}. * {@link ConnectionConfigurationBuilder#setHostnameVerifier(HostnameVerifier)}.
*/ */
public static void setDefaultHostnameVerifier(HostnameVerifier verifier) { public static void setDefaultHostnameVerifier(HostnameVerifier verifier) {
defaultHostnameVerififer = verifier; defaultHostnameVerififer = verifier;

View File

@ -30,7 +30,7 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder;
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
@ -53,10 +53,11 @@ public class TLSUtils {
* This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0. * This method requires the underlying OS to support all of TLSv1.2 , 1.1 and 1.0.
* </p> * </p>
* *
* @param conf the configuration to apply this setting to * @param builder the configuration builder to apply this setting to
*/ */
public static void setTLSOnly(ConnectionConfiguration conf) { public static <B extends ConnectionConfigurationBuilder<B,?>> B setTLSOnly(B builder) {
conf.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 });
return builder;
} }
/** /**
@ -69,10 +70,11 @@ public class TLSUtils {
* TLSv1.1. * TLSv1.1.
* </p> * </p>
* *
* @param conf the configuration to apply this setting to * @param builder the configuration builder to apply this setting to
*/ */
public static void setSSLv3AndTLSOnly(ConnectionConfiguration conf) { public static <B extends ConnectionConfigurationBuilder<B,?>> B setSSLv3AndTLSOnly(B builder) {
conf.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 });
return builder;
} }
/** /**
@ -82,14 +84,15 @@ public class TLSUtils {
* {@link AcceptAllTrustManager}. Only use this method if you understand the implications. * {@link AcceptAllTrustManager}. Only use this method if you understand the implications.
* </p> * </p>
* *
* @param conf * @param builder
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
* @throws KeyManagementException * @throws KeyManagementException
*/ */
public static void acceptAllCertificates(ConnectionConfiguration conf) throws NoSuchAlgorithmException, KeyManagementException { public static <B extends ConnectionConfigurationBuilder<B,?>> B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext context = SSLContext.getInstance(TLS); SSLContext context = SSLContext.getInstance(TLS);
context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom());
conf.setCustomSSLContext(context); builder.setCustomSSLContext(context);
return builder;
} }
public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket,

View File

@ -354,7 +354,7 @@ public class ChatConnectionTest {
try { try {
con.connect(); con.connect();
con.login("me", "secret"); con.login();
} catch (Exception e) { } catch (Exception e) {
// No need for handling in a dummy connection. // No need for handling in a dummy connection.
} }

View File

@ -23,6 +23,7 @@ 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.ConnectionConfiguration.ConnectionConfigurationBuilder;
import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PlainStreamElement; import org.jivesoftware.smack.packet.PlainStreamElement;
import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.TopLevelStreamElement;
@ -53,8 +54,13 @@ public class DummyConnection extends AbstractXMPPConnection {
private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>(); private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>();
public static ConnectionConfigurationBuilder<?,?> getDummyConfigurationBuilder() {
return DummyConnectionConfiguration.builder().setServiceName("example.org").setUsernameAndPassword("dummy",
"dummypass");
}
public DummyConnection() { public DummyConnection() {
this(new ConnectionConfiguration("example.com")); this(getDummyConfigurationBuilder().build());
} }
public DummyConnection(ConnectionConfiguration configuration) { public DummyConnection(ConnectionConfiguration configuration) {
@ -63,12 +69,16 @@ public class DummyConnection extends AbstractXMPPConnection {
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) { for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
listener.connectionCreated(this); listener.connectionCreated(this);
} }
connected = true; user = config.getUsername()
user = "dummy@" + config.getServiceName() + "/Test"; + "@"
+ config.getServiceName()
+ "/"
+ (config.getResource() != null ? config.getResource() : "Test");
} }
@Override @Override
protected void connectInternal() { protected void connectInternal() {
connected = true;
connectionID = "dummy-" + new Random(new Date().getTime()).nextInt(); connectionID = "dummy-" + new Random(new Date().getTime()).nextInt();
if (reconnect) { if (reconnect) {
@ -130,19 +140,13 @@ public class DummyConnection extends AbstractXMPPConnection {
} }
@Override @Override
public void login(String username, String password, String resource) protected void loginNonAnonymously()
throws XMPPException { throws XMPPException {
if (!isConnected()) { user = config.getUsername()
throw new IllegalStateException("Not connected to server.");
}
if (isAuthenticated()) {
throw new IllegalStateException("Already logged in to server.");
}
user = (username != null ? username : "dummy")
+ "@" + "@"
+ config.getServiceName() + config.getServiceName()
+ "/" + "/"
+ (resource != null ? resource : "Test"); + (config.getResource() != null ? config.getResource() : "Test");
roster = new Roster(this); roster = new Roster(this);
anonymous = false; anonymous = false;
authenticated = true; authenticated = true;
@ -226,4 +230,32 @@ public class DummyConnection extends AbstractXMPPConnection {
invokePacketCollectorsAndNotifyRecvListeners(packet); invokePacketCollectorsAndNotifyRecvListeners(packet);
} }
public static class DummyConnectionConfiguration extends ConnectionConfiguration {
protected DummyConnectionConfiguration(DummyConnectionConfigurationBuilder builder) {
super(builder);
}
public static DummyConnectionConfigurationBuilder builder() {
return new DummyConnectionConfigurationBuilder();
}
public static class DummyConnectionConfigurationBuilder
extends
ConnectionConfigurationBuilder<DummyConnectionConfigurationBuilder, DummyConnectionConfiguration> {
private DummyConnectionConfigurationBuilder() {
}
@Override
public DummyConnectionConfiguration build() {
return new DummyConnectionConfiguration(this);
}
@Override
protected DummyConnectionConfigurationBuilder getThis() {
return this;
}
}
}
} }

View File

@ -61,7 +61,7 @@ public class RosterTest {
connection = new DummyConnection(); connection = new DummyConnection();
connection.connect(); connection.connect();
connection.login("rostertest", "secret"); connection.login();
rosterListener = new TestRosterListener(); rosterListener = new TestRosterListener();
connection.getRoster().addRosterListener(rosterListener); connection.getRoster().addRosterListener(rosterListener);
} }

View File

@ -26,6 +26,7 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Packet;
@ -64,12 +65,12 @@ public class RosterVersioningTest {
DirectoryRosterStore store = DirectoryRosterStore.init(tmpFolder.newFolder("store")); DirectoryRosterStore store = DirectoryRosterStore.init(tmpFolder.newFolder("store"));
populateStore(store); populateStore(store);
ConnectionConfiguration conf = new ConnectionConfiguration("dummy"); ConnectionConfigurationBuilder<?, ?> builder = DummyConnection.getDummyConfigurationBuilder();
conf.setRosterStore(store); builder.setRosterStore(store);
connection = new DummyConnection(conf); connection = new DummyConnection(builder.build());
connection.connect(); connection.connect();
connection.login("rostertest", "secret"); connection.login();
} }
@After @After

View File

@ -36,7 +36,7 @@ public class FileTransferNegotiatorTest {
connection = new DummyConnection(); connection = new DummyConnection();
connection.connect(); connection.connect();
connection.login("me", "secret"); connection.login();
ServiceDiscoveryManager.getInstanceFor(connection); ServiceDiscoveryManager.getInstanceFor(connection);
} }

View File

@ -42,6 +42,7 @@ public class LastActivityTest extends InitExtensions {
.namespace(LastActivity.NAMESPACE); .namespace(LastActivity.NAMESPACE);
DummyConnection c = new DummyConnection(); DummyConnection c = new DummyConnection();
c.connect();
IQ lastRequest = (IQ) PacketParserUtils.parseStanza(xml.asString()); IQ lastRequest = (IQ) PacketParserUtils.parseStanza(xml.asString());
assertTrue(lastRequest instanceof LastActivity); assertTrue(lastRequest instanceof LastActivity);

View File

@ -35,6 +35,7 @@ public class VersionTest {
+ "</iq>"; + "</iq>";
// @formatter:on // @formatter:on
DummyConnection con = new DummyConnection(); DummyConnection con = new DummyConnection();
con.connect();
// Enable version replys for this connection // Enable version replys for this connection
VersionManager.setAutoAppendSmackVersion(false); VersionManager.setAutoAppendSmackVersion(false);

View File

@ -53,7 +53,7 @@ public class PingTest extends InitExtensions {
+ "</iq>"; + "</iq>";
// @formatter:on // @formatter:on
DummyConnection con = new DummyConnection(); DummyConnection con = new DummyConnection();
con.connect();
// Enable ping for this connection // Enable ping for this connection
PingManager.getInstanceFor(con); PingManager.getInstanceFor(con);
IQ pingRequest = (IQ) PacketParserUtils.parseStanza(control); IQ pingRequest = (IQ) PacketParserUtils.parseStanza(control);
@ -236,7 +236,7 @@ public class PingTest extends InitExtensions {
private static ThreadedDummyConnection getAuthentiactedDummyConnection() throws SmackException, IOException, XMPPException { private static ThreadedDummyConnection getAuthentiactedDummyConnection() throws SmackException, IOException, XMPPException {
ThreadedDummyConnection connection = new ThreadedDummyConnection(); ThreadedDummyConnection connection = new ThreadedDummyConnection();
connection.connect(); connection.connect();
connection.login("foo", "bar"); connection.login();
return connection; return connection;
} }
@ -252,7 +252,7 @@ public class PingTest extends InitExtensions {
DummyConnection con = new DummyConnection(); DummyConnection con = new DummyConnection();
con.setPacketReplyTimeout(500); con.setPacketReplyTimeout(500);
con.connect(); con.connect();
con.login("foo", "bar"); con.login();
return con; return con;
} }
} }

View File

@ -47,7 +47,7 @@ public class ItemValidationTest extends InitExtensions {
connection = new ThreadedDummyConnection(); connection = new ThreadedDummyConnection();
connection.connect(); connection.connect();
connection.login("me", "secret"); connection.login();
} }
@After @After

View File

@ -70,6 +70,7 @@ public class DeliveryReceiptTest extends InitExtensions {
@Test @Test
public void receiptManagerListenerTest() throws Exception { public void receiptManagerListenerTest() throws Exception {
DummyConnection c = new DummyConnection(); DummyConnection c = new DummyConnection();
c.connect();
// Ensure SDM is created for this connection // Ensure SDM is created for this connection
ServiceDiscoveryManager.getInstanceFor(c); ServiceDiscoveryManager.getInstanceFor(c);
DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c); DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c);
@ -101,6 +102,7 @@ public class DeliveryReceiptTest extends InitExtensions {
@Test @Test
public void receiptManagerAutoReplyTest() throws Exception { public void receiptManagerAutoReplyTest() throws Exception {
DummyConnection c = new DummyConnection(); DummyConnection c = new DummyConnection();
c.connect();
// Ensure SDM is created for this connection // Ensure SDM is created for this connection
ServiceDiscoveryManager.getInstanceFor(c); ServiceDiscoveryManager.getInstanceFor(c);
DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c); DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c);

View File

@ -86,7 +86,6 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.PasswordCallback;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -115,7 +114,6 @@ import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
@ -256,81 +254,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
*/ */
private final Set<PacketFilter> requestAckPredicates = new LinkedHashSet<PacketFilter>(); private final Set<PacketFilter> requestAckPredicates = new LinkedHashSet<PacketFilter>();
private final XMPPTCPConnectionConfiguration config;
/** /**
* Creates a new connection to the specified XMPP server. A DNS SRV lookup will be * Creates a new XMPP connection over TCP (optionally using proxies).
* performed to determine the IP address and port corresponding to the * <p>
* service name; if that lookup fails, it's assumed that server resides at * Note that XMPPTCPConnection constructors do not establish a connection to the server
* <tt>serviceName</tt> with the default port of 5222. Encrypted connections (TLS) * and you must call {@link #connect()}.
* will be used if available, stream compression is disabled, and standard SASL * </p>
* mechanisms will be used for authentication.<p> *
* <p/> * @param config the connection configuration.
*/
public XMPPTCPConnection(XMPPTCPConnectionConfiguration config) {
super(config);
this.config = config;
}
/**
* Creates a new XMPP connection over TCP.
* <p>
* This is the simplest constructor for connecting to an XMPP server. Alternatively, * This is the simplest constructor for connecting to an XMPP server. Alternatively,
* you can get fine-grained control over connection settings using the * you can get fine-grained control over connection settings using the
* {@link #XMPPTCPConnection(ConnectionConfiguration)} constructor.<p> * {@link #XMPPTCPConnection(XMPPTCPConnectionConfiguration)} constructor.
* <p/> * </p>
* Note that XMPPTCPConnection constructors do not establish a connection to the server * @param username
* and you must call {@link #connect()}.<p> * @param password
* <p/> * @param serviceName
* The CallbackHandler will only be used if the connection requires the client provide
* an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback
* to prompt for a password to unlock the keystore containing the SSL certificate.
*
* @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>.
* @param callbackHandler the CallbackHandler used to prompt for the password to the keystore.
*/ */
public XMPPTCPConnection(String serviceName, CallbackHandler callbackHandler) { public XMPPTCPConnection(String username, String password, String serviceName) {
// Create the configuration for this new connection this(XMPPTCPConnectionConfiguration.builder().setUsernameAndPassword(username, password).setServiceName(
super(new ConnectionConfiguration(serviceName)); serviceName).build());
config.setCallbackHandler(callbackHandler);
}
/**
* Creates a new XMPP connection in the same way {@link #XMPPTCPConnection(String,CallbackHandler)} does, but
* with no callback handler for password prompting of the keystore. This will work
* in most cases, provided the client is not required to provide a certificate to
* the server.
*
* @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>.
*/
public XMPPTCPConnection(String serviceName) {
// Create the configuration for this new connection
super(new ConnectionConfiguration(serviceName));
}
/**
* Creates a new XMPP connection in the same way {@link #XMPPTCPConnection(ConnectionConfiguration,CallbackHandler)} does, but
* with no callback handler for password prompting of the keystore. This will work
* in most cases, provided the client is not required to provide a certificate to
* the server.
*
*
* @param config the connection configuration.
*/
public XMPPTCPConnection(ConnectionConfiguration config) {
super(config);
}
/**
* Creates a new XMPP connection using the specified connection configuration.<p>
* <p/>
* Manually specifying connection configuration information is suitable for
* advanced users of the API. In many cases, using the
* {@link #XMPPTCPConnection(String)} constructor is a better approach.<p>
* <p/>
* Note that XMPPTCPConnection constructors do not establish a connection to the server
* and you must call {@link #connect()}.<p>
* <p/>
*
* The CallbackHandler will only be used if the connection requires the client provide
* an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback
* to prompt for a password to unlock the keystore containing the SSL certificate.
*
* @param config the connection configuration.
* @param callbackHandler the CallbackHandler used to prompt for the password to the keystore.
*/
public XMPPTCPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) {
super(config);
config.setCallbackHandler(callbackHandler);
} }
@Override @Override
@ -361,19 +314,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
@Override @Override
public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, IOException { protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException {
if (!isConnected()) { packetWriter.throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
throw new NotConnectedException(); }
@Override
protected void throwAlreadyConnectedExceptionIfAppropriate() throws AlreadyConnectedException {
if (isConnected() && !disconnectedButResumeable) {
throw new AlreadyConnectedException();
} }
if (authenticated && !disconnectedButResumeable) { }
@Override
protected void throwAlreadyLoggedInExceptionIfAppropriate() throws AlreadyLoggedInException {
if (isAuthenticated() && !disconnectedButResumeable) {
throw new AlreadyLoggedInException(); throw new AlreadyLoggedInException();
} }
}
// Do partial version of nameprep on the username. @Override
if (username != null) { protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException {
username = username.toLowerCase(Locale.US).trim(); // Reset the flag in case it was set
} disconnectedButResumeable = false;
super.afterSuccessfulLogin(resumed);
}
@Override
protected synchronized void loginNonAnonymously() throws XMPPException, SmackException, IOException {
String password = config.getPassword();
String resource = config.getResource();
String username = config.getUsername();
if (saslAuthentication.hasNonAnonymousAuthentication()) { if (saslAuthentication.hasNonAnonymousAuthentication()) {
// Authenticate using SASL // Authenticate using SASL
if (password != null) { if (password != null) {
@ -396,7 +366,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId)); smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId));
if (smResumedSyncPoint.wasSuccessful()) { if (smResumedSyncPoint.wasSuccessful()) {
// We successfully resumed the stream, be done here // We successfully resumed the stream, be done here
afterSuccessfulLogin(false, true); afterSuccessfulLogin(true);
return; return;
} }
// SM resumption failed, what Smack does here is to report success of // SM resumption failed, what Smack does here is to report success of
@ -435,20 +405,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
sendPacketInternal(stanza); sendPacketInternal(stanza);
} }
// Stores the authentication for future reconnection afterSuccessfulLogin(false);
setLoginInfo(username, password, resource);
afterSuccessfulLogin(false, false);
} }
@Override @Override
public synchronized void loginAnonymously() throws XMPPException, SmackException, IOException { public synchronized void loginAnonymously() throws XMPPException, SmackException, IOException {
if (!isConnected()) {
throw new NotConnectedException();
}
if (authenticated) {
throw new AlreadyLoggedInException();
}
// Wait with SASL auth until the SASL mechanisms have been received // Wait with SASL auth until the SASL mechanisms have been received
saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
@ -466,7 +427,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
bindResourceAndEstablishSession(null); bindResourceAndEstablishSession(null);
afterSuccessfulLogin(true, false); afterSuccessfulLogin(false);
} }
@Override @Override
@ -499,11 +460,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
/** /**
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza. * Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
*/ */
public void instantShutdown() { public synchronized void instantShutdown() {
shutdown(true); shutdown(true);
} }
private void shutdown(boolean instant) { private void shutdown(boolean instant) {
if (disconnectedButResumeable) {
return;
}
if (packetReader != null) { if (packetReader != null) {
packetReader.shutdown(); packetReader.shutdown();
} }
@ -522,7 +486,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
LOGGER.log(Level.WARNING, "shutdown", e); LOGGER.log(Level.WARNING, "shutdown", e);
} }
setWasAuthenticated(authenticated); setWasAuthenticated();
// If we are able to resume the stream, then don't set // If we are able to resume the stream, then don't set
// connected/authenticated/usingTLS to false since we like behave like we are still // connected/authenticated/usingTLS to false since we like behave like we are still
// connected (e.g. sendPacket should not throw a NotConnectedException). // connected (e.g. sendPacket should not throw a NotConnectedException).
@ -563,12 +527,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
private void connectUsingConfiguration(ConnectionConfiguration config) throws SmackException, IOException { private void connectUsingConfiguration(ConnectionConfiguration config) throws SmackException, IOException {
try { try {
maybeResolveDns(); populateHostAddresses();
} }
catch (Exception e) { catch (Exception e) {
throw new SmackException(e); throw new SmackException(e);
} }
Iterator<HostAddress> it = config.getHostAddresses().iterator(); Iterator<HostAddress> it = hostAddresses.iterator();
List<HostAddress> failedAddresses = new LinkedList<HostAddress>(); List<HostAddress> failedAddresses = new LinkedList<HostAddress>();
while (it.hasNext()) { while (it.hasNext()) {
Exception exception = null; Exception exception = null;
@ -853,9 +817,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
*/ */
@Override @Override
protected void connectInternal() throws SmackException, IOException, XMPPException { protected void connectInternal() throws SmackException, IOException, XMPPException {
if (connected && !disconnectedButResumeable) { throwAlreadyConnectedExceptionIfAppropriate();
throw new AlreadyConnectedException();
}
// Establishes the connection, readers and writers // Establishes the connection, readers and writers
connectUsingConfiguration(config); connectUsingConfiguration(config);
@ -869,14 +831,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// Automatically makes the login if the user was previously connected successfully // Automatically makes the login if the user was previously connected successfully
// to the server and the connection was terminated abruptly // to the server and the connection was terminated abruptly
if (wasAuthenticated) { if (wasAuthenticated) {
// Make the login login();
if (isAnonymous()) {
// Make the anonymous login
loginAnonymously();
}
else {
login(config.getUsername(), config.getPassword(), config.getResource());
}
notifyReconnection(); notifyReconnection();
} }
} }
@ -1052,21 +1007,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
case "stream": case "stream":
// We found an opening stream. // We found an opening stream.
if ("jabber:client".equals(parser.getNamespace(null))) { if ("jabber:client".equals(parser.getNamespace(null))) {
// Get the connection id. connectionID = parser.getAttributeValue("", "id");
for (int i=0; i<parser.getAttributeCount(); i++) { String reportedServiceName = parser.getAttributeValue("", "from");
if (parser.getAttributeName(i).equals("id")) { assert(reportedServiceName.equals(config.getServiceName()));
// Save the connectionID
connectionID = parser.getAttributeValue(i);
}
// According to RFC 6120 4.7.1 response
// stream headers in c2s and s2s of the
// receiving entity MUST include the 'from'
// attribute.
else if (parser.getAttributeName(i).equals("from")) {
// Use the server name that the server says that it is.
setServiceName(parser.getAttributeValue(i));
}
}
} }
break; break;
case "error": case "error":
@ -1290,7 +1233,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
return shutdownTimestamp != null; return shutdownTimestamp != null;
} }
private void throwNotConnectedExceptionIfDoneAndResumptionNotPossible() throws NotConnectedException { protected void throwNotConnectedExceptionIfDoneAndResumptionNotPossible() throws NotConnectedException {
if (done() && !isSmResumptionPossible()) { if (done() && !isSmResumptionPossible()) {
// Don't throw a NotConnectedException is there is an resumable stream available // Don't throw a NotConnectedException is there is an resumable stream available
throw new NotConnectedException(); throw new NotConnectedException();

View File

@ -0,0 +1,76 @@
/**
*
* 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;
import org.jivesoftware.smack.ConnectionConfiguration;
public class XMPPTCPConnectionConfiguration extends ConnectionConfiguration {
private final boolean compressionEnabled;
private XMPPTCPConnectionConfiguration(XMPPTCPConnectionConfigurationBuilder builder) {
super(builder);
compressionEnabled = builder.compressionEnabled;
}
/**
* Returns true if the connection is going to use stream compression. Stream compression
* will be requested after TLS was established (if TLS was enabled) and only if the server
* offered stream compression. With stream compression network traffic can be reduced
* up to 90%. By default compression is disabled.
*
* @return true if the connection is going to use stream compression.
*/
@Override
public boolean isCompressionEnabled() {
return compressionEnabled;
}
public static XMPPTCPConnectionConfigurationBuilder builder() {
return new XMPPTCPConnectionConfigurationBuilder();
}
public static class XMPPTCPConnectionConfigurationBuilder extends ConnectionConfigurationBuilder<XMPPTCPConnectionConfigurationBuilder, XMPPTCPConnectionConfiguration> {
private boolean compressionEnabled = false;
private XMPPTCPConnectionConfigurationBuilder() {
}
/**
* Sets if the connection is going to use stream compression. Stream compression
* will be requested after TLS was established (if TLS was enabled) and only if the server
* offered stream compression. With stream compression network traffic can be reduced
* up to 90%. By default compression is disabled.
*
* @param compressionEnabled if the connection is going to use stream compression.
*/
public XMPPTCPConnectionConfigurationBuilder setCompressionEnabled(boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
return this;
}
@Override
protected XMPPTCPConnectionConfigurationBuilder getThis() {
return this;
}
@Override
public XMPPTCPConnectionConfiguration build() {
return new XMPPTCPConnectionConfiguration(this);
}
}
}

View File

@ -45,7 +45,7 @@ public class PacketWriterTest {
@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("user", "pass", "example.org");
final PacketWriter pw = connection.new PacketWriter(); final PacketWriter pw = connection.new PacketWriter();
connection.packetWriter = pw; connection.packetWriter = pw;
connection.packetReader = connection.new PacketReader(); connection.packetReader = connection.new PacketReader();

View File

@ -38,7 +38,7 @@ public class RosterOfflineTest {
@Before @Before
public void setup() throws XMPPException, SmackException { public void setup() throws XMPPException, SmackException {
this.connection = new XMPPTCPConnection("localhost"); this.connection = new XMPPTCPConnection("user", "pass", "example.org");
assertFalse(connection.isConnected()); assertFalse(connection.isConnected());
roster = connection.getRoster(); roster = connection.getRoster();