From c81cd3456146d788080923d0acc2ac7371f82c97 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 9 Nov 2014 18:30:16 +0100 Subject: [PATCH] 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 --- documentation/connections.md | 17 +- documentation/extensions/rosterexchange.md | 19 +- documentation/gettingstarted.md | 24 +- documentation/overview.md | 4 +- .../smack/bosh/BOSHConfiguration.java | 115 ++- .../smack/bosh/XMPPBOSHConnection.java | 53 +- .../smack/AbstractXMPPConnection.java | 122 ++- .../smack/ConnectionConfiguration.java | 734 +++++++++--------- .../smack/SmackConfiguration.java | 3 +- .../org/jivesoftware/smack/util/TLSUtils.java | 23 +- .../smack/ChatConnectionTest.java | 2 +- .../jivesoftware/smack/DummyConnection.java | 56 +- .../org/jivesoftware/smack/RosterTest.java | 2 +- .../smack/RosterVersioningTest.java | 9 +- .../FileTransferNegotiatorTest.java | 2 +- .../smackx/iqlast/LastActivityTest.java | 1 + .../smackx/iqversion/VersionTest.java | 1 + .../jivesoftware/smackx/ping/PingTest.java | 6 +- .../smackx/pubsub/ItemValidationTest.java | 2 +- .../smackx/receipts/DeliveryReceiptTest.java | 2 + .../smack/tcp/XMPPTCPConnection.java | 191 ++--- .../tcp/XMPPTCPConnectionConfiguration.java | 76 ++ .../smack/tcp/PacketWriterTest.java | 2 +- .../smack/tcp/RosterOfflineTest.java | 2 +- 24 files changed, 760 insertions(+), 708 deletions(-) create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnectionConfiguration.java diff --git a/documentation/connections.md b/documentation/connections.md index 27f5eb5da..26f6bcc55 100644 --- a/documentation/connections.md +++ b/documentation/connections.md @@ -28,18 +28,21 @@ Connect and Disconnect ---------------------- ``` -// Create the configuration for this new connection_ -ConnectionConfiguration config = new ConnectionConfiguration("jabber.org", 5222); +// Create the configuration for this new connection +XMPPTCPConnectionConfigurationBuilder configBuilder = XMPPTCPConnectionConfiguration.builder(); +configBuilder.setUsernameAndPassword("username", "password"); +configBuilder.setResource("SomeResource"); +configBuilder.setServiceName("jabber.org"); -AbstractXMPPConnection connection = new XMPPTCPConnection(config); -// Connect to the server_ +AbstractXMPPConnection connection = new XMPPTCPConnection(configBuilder.build()); +// Connect to the server connection.connect(); -// Log into the server_ -connection.login("username", "password", "SomeResource"); +// Log into the server +connection.login(); ... -// Disconnect from the server_ +// Disconnect from the server connection.disconnect(); ``` diff --git a/documentation/extensions/rosterexchange.md b/documentation/extensions/rosterexchange.md index bf32906a3..a8aa171ca 100644 --- a/documentation/extensions/rosterexchange.md +++ b/documentation/extensions/rosterexchange.md @@ -34,9 +34,7 @@ receive the roster entries. In this example we can see how user1 sends his roster to user2. ``` -// Connect to the server and log in -conn1 = new XMPPConnection(host); -conn1.login(server_user1, pass1); +XMPPConnection conn1 = … // Create a new roster exchange manager on 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. ``` -// Connect to the server and log in -conn1 = new XMPPConnection(host); -conn1.login(server_user1, pass1); +XMPPConnection conn1 = … // Create a new roster exchange manager on 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. ``` -// Connect to the server and log in -conn1 = new XMPPConnection(host); -conn1.login(server_user1, pass1); +XMPPConnection conn1 = … // Create a new roster exchange manager on 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 -conn1 = new XMPPConnection(host); -conn1.login(server_user1, pass1); -conn2 = new XMPPConnection(host); -conn2.login(server_user2, pass2); +XMPPConnection conn1 = … +XMPPConnection conn2 = … + final Roster user2_roster = conn2.getRoster(); // Create a RosterExchangeManager that will help user2 to listen and accept diff --git a/documentation/gettingstarted.md b/documentation/gettingstarted.md index 2e73b11b9..f8f75ad02 100644 --- a/documentation/gettingstarted.md +++ b/documentation/gettingstarted.md @@ -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: ``` -// Create a connection to the jabber.org server._ -XMPPConnection conn1 = **new** XMPPTCPConnection("jabber.org"); +// Create a connection to the jabber.org server. +AbstractXMPPConnection conn1 = **new** XMPPTCPConnection("username", "password" "jabber.org"); conn1.connect(); -// Create a connection to the jabber.org server on a specific port._ -ConnectionConfiguration config = new ConnectionConfiguration("jabber.org", 5222); -XMPPConnection conn2 = **new** XMPPTCPConnection(config); +// Create a connection to the jabber.org server on a specific port. +XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder() + .setUsernameAndPassword("username", "password") + .setServiceName("jabber.org") + .setHost("earl.jabber.org") + .setPort("8222") + .build(); + +AbstractXMPPConnection conn2 = **new** XMPPTCPConnection(config); 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 [XMPPConnection Management](connections.html) for full details. -Once you've created a connection, you should login using a username and -password with the `XMPPConnection.login(String username, String password)` -method. Once you've logged in, you can being chatting with other users by -creating new `Chat` or `GroupChat` objects. +Once you've created a connection, you should login with the +`XMPPConnection.login()` method. Once you've logged in, you can being +chatting with other users by creating new `Chat` or `GroupChat` +objects. Working with the Roster ---------------------- diff --git a/documentation/overview.md b/documentation/overview.md index 57ee21b3c..4b2976385 100644 --- a/documentation/overview.md +++ b/documentation/overview.md @@ -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: ```java - AbstractXMPPConnection connection = new XMPPTCPConnection("jabber.org"); + AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org"); connection.connect(); - connection.login("mtucker", "password"); + connection.login(); Chat chat = ChatManager.getInstanceFor(connection) .createChat("jsmith@jivesoftware.com", new MessageListener() { diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHConfiguration.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHConfiguration.java index a1f1dbc9a..a586eaca2 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHConfiguration.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/BOSHConfiguration.java @@ -22,7 +22,6 @@ import java.net.URISyntaxException; import org.jivesoftware.smack.ConnectionConfiguration; 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 @@ -33,60 +32,17 @@ import org.jivesoftware.smack.util.dns.HostAddress; */ public class BOSHConfiguration extends ConnectionConfiguration { - private boolean ssl; - private String file; + private final boolean https; + private final String file; - public BOSHConfiguration(String xmppDomain) { - super(xmppDomain, 7070); - ssl = false; - file = "/http-bind/"; - } - - public BOSHConfiguration(String xmppDomain, int port) { - 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 : "/"); + private BOSHConfiguration(BOSHConfigurationBuilder builder) { + super(builder); + https = builder.https; + if (builder.file.charAt(0) != '/') { + file = '/' + builder.file; + } else { + file = builder.file; + } } public boolean isProxyEnabled() { @@ -105,24 +61,47 @@ public class BOSHConfiguration extends ConnectionConfiguration { return (proxy != null ? proxy.getProxyPort() : 8080); } - public boolean isUsingSSL() { - return ssl; + public boolean isUsingHTTPS() { + return https; } public URI getURI() throws URISyntaxException { - if (file.charAt(0) != '/') { - file = '/' + file; + return new URI((https ? "https://" : "http://") + this.host + ":" + this.port + file); + } + + public static BOSHConfigurationBuilder builder() { + return new BOSHConfigurationBuilder(); + } + + public static class BOSHConfigurationBuilder extends ConnectionConfigurationBuilder { + private boolean https; + private String file; + + private BOSHConfigurationBuilder() { } - String host; - int port; - if (hostAddresses != null) { - HostAddress hostAddress = hostAddresses.get(0); - host = hostAddress.getFQDN(); - port = hostAddress.getPort(); - } else { - host = getServiceName(); - port = 80; + + public BOSHConfigurationBuilder setUseHttps(boolean useHttps) { + https = useHttps; + return this; + } + + public BOSHConfigurationBuilder useHttps() { + return setUseHttps(true); + } + + 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); } } diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java index da03f04a0..db20c06f2 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.io.PipedReader; import java.io.PipedWriter; import java.io.Writer; -import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; @@ -30,7 +29,6 @@ import javax.security.sasl.SaslException; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; -import org.jivesoftware.smack.SmackException.AlreadyLoggedInException; import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.XMPPConnection; @@ -106,6 +104,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { /** * 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 * (e.g. false for http://domain.lt:7070/http-bind). * @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 * (e.g. domain.lt for the user alice@domain.lt) */ - public XMPPBOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) { - super(new BOSHConfiguration(https, host, port, filePath, xmppDomain)); - this.config = (BOSHConfiguration) getConfiguration(); + public XMPPBOSHConnection(String username, String password, boolean https, String host, int port, String filePath, String xmppDomain) { + this(BOSHConfiguration.builder().setUseHttps(https).setHost(host) + .setPort(port).setFile(filePath).setServiceName(xmppDomain) + .setUsernameAndPassword(username, password).build()); } /** @@ -134,9 +135,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { @Override protected void connectInternal() throws SmackException { - if (connected) { - throw new IllegalStateException("Already connected to a server."); - } done = false; try { // Ensure a clean starting state @@ -224,18 +222,12 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { return false; } - public void login(String username, String password, String resource) + @Override + protected void loginNonAnonymously() throws XMPPException, SmackException, IOException { - if (!isConnected()) { - throw new NotConnectedException(); - } - if (authenticated) { - throw new AlreadyLoggedInException(); - } - - // Do partial version of nameprep on the username. - username = username.toLowerCase(Locale.US).trim(); - + String password = config.getPassword(); + String resource = config.getResource(); + String username = config.getUsername(); if (saslAuthentication.hasNonAnonymousAuthentication()) { // Authenticate using SASL if (password != null) { @@ -249,19 +241,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { bindResourceAndEstablishSession(resource); - // Stores the authentication for future reconnection - setLoginInfo(username, password, resource); - afterSuccessfulLogin(false, false); + afterSuccessfulLogin(false); } - public void loginAnonymously() throws XMPPException, SmackException, IOException { - if (!isConnected()) { - throw new NotConnectedException(); - } - if (authenticated) { - throw new AlreadyLoggedInException(); - } - + @Override + protected void loginAnonymously() throws XMPPException, SmackException, IOException { // Wait with SASL auth until the SASL mechanisms have been received saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); @@ -275,7 +259,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { bindResourceAndEstablishSession(null); - afterSuccessfulLogin(true, false); + afterSuccessfulLogin(false); } @Override @@ -313,7 +297,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { */ @Override protected void shutdown() { - setWasAuthenticated(authenticated); + setWasAuthenticated(); authID = null; sessionID = null; done = true; @@ -508,10 +492,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { else { try { if (wasAuthenticated) { - connection.login( - config.getUsername(), - config.getPassword(), - config.getResource()); + connection.login(); } for (ConnectionListener listener : getConnectionListeners()) { listener.reconnectionSuccessful(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 6a4c7ec69..1f2f27ddc 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -19,6 +19,7 @@ package org.jivesoftware.smack; import java.io.IOException; import java.io.Reader; import java.io.Writer; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; @@ -39,7 +40,10 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; +import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder; 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.NotConnectedException; 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.rosterstore.RosterStore; import org.jivesoftware.smack.util.Async; +import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.dns.HostAddress; import org.jxmpp.util.XmppStringUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -255,8 +261,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { */ protected boolean wasAuthenticated = false; - private boolean anonymous = false; - /** * Create a new XMPPConnection to a XMPP server. * @@ -272,6 +276,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { @Override public String getServiceName() { + if (serviceName != null) { + return serviceName; + } return config.getServiceName(); } @@ -312,6 +319,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { * @throws ConnectionException with detailed information about the failed connection. */ public void connect() throws SmackException, IOException, XMPPException { + throwAlreadyConnectedExceptionIfAppropriate(); saslAuthentication.init(); saslFeatureReceived.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. * * 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 - * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. + * {@link ConnectionConfigurationBuilder#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)}. + * {@link ConnectionConfigurationBuilder#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. * For more advanced login settings see {@link ConnectionConfiguration}. * - * @param username the username. - * @param password the password or null if using a CallbackHandler. * @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 void login(String username, String password) throws XMPPException, SmackException, IOException { - login(username, password, "Smack"); + public void login() throws XMPPException, SmackException, IOException { + throwNotConnectedExceptionIfAppropriate(); + throwAlreadyLoggedInExceptionIfAppropriate(); + if (isAnonymous()) { + loginAnonymously(); + } else { + loginNonAnonymously(); + } } - /** - * 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.

- * - * 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 null 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 loginNonAnonymously() throws XMPPException, SmackException, IOException; + protected abstract void loginAnonymously() throws XMPPException, SmackException, IOException; @Override public final boolean isConnected() { @@ -437,7 +412,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { PacketCollector packetCollector = createPacketCollectorAndSend(new PacketIDFilter(bindResource), bindResource); Bind response = packetCollector.nextResultOrThrow(); user = response.getJid(); - setServiceName(XmppStringUtils.parseDomain(user)); + serviceName = XmppStringUtils.parseDomain(user); if (hasFeature(Session.ELEMENT, Session.NAMESPACE) && !getConfiguration().isLegacySessionDisabled()) { 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. this.authenticated = true; - this.anonymous = anonymous; // If debugging is enabled, change the the debug window title to include the // name we are now logged-in as. @@ -476,33 +450,53 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { @Override public boolean isAnonymous() { - return anonymous; + return config.isAnonymous(); } - protected void setServiceName(String serviceName) { - config.setServiceName(serviceName); - } + private String serviceName; - protected void setLoginInfo(String username, String password, String resource) { - config.setLoginInfo(username, password, resource); - } + protected List hostAddresses; - protected void maybeResolveDns() throws Exception { - config.maybeResolveDns(); + protected void populateHostAddresses() throws Exception { + // N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName + if (config.host != null) { + hostAddresses = new ArrayList(1); + HostAddress hostAddress; + hostAddress = new HostAddress(config.host, config.port); + hostAddresses.add(hostAddress); + } else { + hostAddresses = DNSUtil.resolveXMPPDomain(config.serviceName); + } } protected Lock getConnectionLock() { return connectionLock; } - @Override - public void sendPacket(Packet packet) throws NotConnectedException { + protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException { if (!isConnected()) { 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) { throw new IllegalArgumentException("Packet must not be null"); } + throwNotConnectedExceptionIfAppropriate(); switch (fromMode) { case OMITTED: 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 * {@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 if (!wasAuthenticated) { wasAuthenticated = authenticated; @@ -1229,4 +1222,5 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { protected void reportStanzaReceived() { this.lastStanzaReceived = System.currentTimeMillis(); } + } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index 7e215d9af..039f6fcb7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -17,33 +17,23 @@ package org.jivesoftware.smack; +import java.util.Locale; + import org.jivesoftware.smack.packet.Session; import org.jivesoftware.smack.proxy.ProxyInfo; 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.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; 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 - * 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.

- * - * It is also possible to configure if TLS, SASL, and compression are used or not. + * Configuration to use while establishing the connection to the server. * * @author Gaston Dombiak */ -public class ConnectionConfiguration implements Cloneable { +public abstract class ConnectionConfiguration { static { // 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 * 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 hostAddresses; - - private String keystorePath; - private String keystoreType; - private String pkcs11Library; - private SSLContext customSSLContext; - - private boolean compressionEnabled = false; + private final String keystorePath; + private final String keystoreType; + private final String pkcs11Library; + private final SSLContext customSSLContext; /** * 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 - private SocketFactory socketFactory; - - // Holds the authentication information for future reconnections - private String username; - private String password; - private String resource; - private boolean sendPresence = true; - private boolean rosterLoadedAtLogin = true; - private boolean legacySessionDisabled = false; - private boolean useDnsSrvRr = true; - private SecurityMode securityMode = SecurityMode.enabled; + private final SocketFactory socketFactory; + + private final String username; + private final String password; + private final String resource; + private final boolean sendPresence; + private final boolean rosterLoadedAtLogin; + private final boolean legacySessionDisabled; + private final SecurityMode securityMode; /** * */ - 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 */ - private RosterStore rosterStore; + private final RosterStore rosterStore; // Holds the proxy information (such as proxyhost, proxyport, username, password etc) - protected ProxyInfo proxy; + protected final ProxyInfo proxy; - /** - * Creates a new ConnectionConfiguration for the specified service name. - * 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. - */ - public ConnectionConfiguration(String serviceName) { - init(serviceName, ProxyInfo.forDefaultProxy()); - } - - /** - * 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"); + protected ConnectionConfiguration(ConnectionConfigurationBuilder builder) { + if (builder.username != null) { + // Do partial version of nameprep on the username. + username = builder.username.toLowerCase(Locale.US).trim(); + } else { + username = null; + } + password = builder.password; + 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"); } - this.serviceName = serviceName; - this.proxy = proxy; - keystorePath = System.getProperty("javax.net.ssl.keyStore"); - keystoreType = "jks"; - pkcs11Library = "pkcs11.config"; - - //Setting the SocketFactory according to proxy supplied - socketFactory = proxy.getSocketFactory(); + // Resource can be null, this means that the server must provide one + resource = builder.resource; + + serviceName = builder.serviceName; + if (serviceName == null) { + 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; } - /** - * Sets the server name, also known as XMPP domain of the target server. - * - * @param serviceName the XMPP domain of the target server. - */ - void setServiceName(String serviceName) { - serviceName = XmppStringUtils.parseDomain(serviceName); - this.serviceName = serviceName; + public boolean isAnonymous() { + return username == null && callbackHandler == null; } /** @@ -239,16 +165,6 @@ public class ConnectionConfiguration implements Cloneable { 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 * certificates that may be used to authenticate the client to the server, @@ -260,17 +176,6 @@ public class ConnectionConfiguration implements Cloneable { 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 null if it's not set. * @@ -280,16 +185,6 @@ public class ConnectionConfiguration implements Cloneable { 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 * Keystore type is PKCS11. @@ -301,17 +196,7 @@ public class ConnectionConfiguration implements Cloneable { } /** - * Sets the PKCS11 library file location, needed when the - * 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 + * Gets the custom SSLContext previously set with {@link ConnectionConfigurationBuilder#setCustomSSLContext(SSLContext)} for * SSL sockets. This is null by default. * * @return the custom SSLContext or null. @@ -320,28 +205,6 @@ public class ConnectionConfiguration implements Cloneable { return this.customSSLContext; } - /** - * Sets a custom SSLContext for creating SSL sockets. - *

- * For more information on how to create a SSLContext see Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager - * - * @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. * @@ -351,15 +214,6 @@ public class ConnectionConfiguration implements Cloneable { 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. * @@ -369,16 +223,6 @@ public class ConnectionConfiguration implements Cloneable { 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 * HostnameVerifier configured with @@ -392,30 +236,6 @@ public class ConnectionConfiguration implements Cloneable { 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 * default the value of {@link SmackConfiguration#DEBUG_ENABLED} is used. @@ -426,38 +246,6 @@ public class ConnectionConfiguration implements Cloneable { 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 false 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 * 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; } - /** - * 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 * 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; } - /** - * 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 * principal information during the SASL authentication. A CallbackHandler @@ -515,19 +281,6 @@ public class ConnectionConfiguration implements Cloneable { return callbackHandler; } - /** - * Sets a CallbackHandler to obtain information, such as the password or - * principal information during the SASL authentication. A CallbackHandler - * will be used ONLY 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. * This is useful when connecting through SOCKS5 proxies. @@ -538,17 +291,6 @@ public class ConnectionConfiguration implements Cloneable { return this.socketFactory; } - public List getHostAddresses() { - return Collections.unmodifiableList(hostAddresses); - } - - /** - * Set the permanent roster store - */ - public void setRosterStore(RosterStore store) { - rosterStore = store; - } - /** * Get the permanent roster store */ @@ -620,25 +362,319 @@ public class ConnectionConfiguration implements Cloneable { return sendPresence; } - void setLoginInfo(String username, String password, String resource) { - this.username = username; - this.password = password; - this.resource = resource; + /** + * 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() { + // Compression for non-TCP connections is always disabled + return false; } - void maybeResolveDns() throws Exception { - if (!useDnsSrvRr) return; - hostAddresses = DNSUtil.resolveXMPPDomain(serviceName); - } + /** + * A builder for XMPP connection configurations. + *

+ * 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 What is the + * "getThis()" trick?. + *

+ * + * @param the builder type parameter. + * @param the resulting connection configuration type parameter. + */ + public static abstract class ConnectionConfigurationBuilder, 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) { - if (StringUtils.isEmpty(host)) { - throw new IllegalArgumentException("host must not be the empty String"); + protected ConnectionConfigurationBuilder() { } - hostAddresses = new ArrayList(1); - HostAddress hostAddress; - hostAddress = new HostAddress(host, port); - hostAddresses.add(hostAddress); - useDnsSrvRr = false; + + /** + * Set the XMPP entities username and password. + *

+ * The username is the localpart of the entities JID, e.g. localpart@example.org. In order to + * create an anonymous connection, call {@link #makeAnonymous} instead. + *

+ * + * @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. + *

+ * 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. + *

+ * + * @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. + *

+ * If resource is null, then the server will automatically create a resource for the + * client. Default resource is "Smack". + *

+ * + * @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 ONLY 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. + *

+ * For more information on how to create a SSLContext see Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager + * + * @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 false 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(); } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java index d94cbe740..37e4b9b5f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java @@ -28,6 +28,7 @@ import java.util.Set; import javax.net.ssl.HostnameVerifier; +import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder; import org.jivesoftware.smack.compression.XMPPInputOutputStream; import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory; 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 * of a TLS certificate. XMPP connections are able to overwrite this settings by supplying a * HostnameVerifier in their ConnecitonConfiguration with - * {@link ConnectionConfiguration#setHostnameVerifier(HostnameVerifier)}. + * {@link ConnectionConfigurationBuilder#setHostnameVerifier(HostnameVerifier)}. */ public static void setDefaultHostnameVerifier(HostnameVerifier verifier) { defaultHostnameVerififer = verifier; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java index 7b1144952..1250a122a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java @@ -30,7 +30,7 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder; 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. *

* - * @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) { - conf.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); + public static > B setTLSOnly(B builder) { + builder.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1 }); + return builder; } /** @@ -69,10 +70,11 @@ public class TLSUtils { * TLSv1.1. *

* - * @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) { - conf.setEnabledSSLProtocols(new String[] { PROTO_TLSV1_2, PROTO_TLSV1_1, PROTO_TLSV1, PROTO_SSL3 }); + public static > B setSSLv3AndTLSOnly(B builder) { + 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. *

* - * @param conf + * @param builder * @throws NoSuchAlgorithmException * @throws KeyManagementException */ - public static void acceptAllCertificates(ConnectionConfiguration conf) throws NoSuchAlgorithmException, KeyManagementException { + public static > B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException { SSLContext context = SSLContext.getInstance(TLS); context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); - conf.setCustomSSLContext(context); + builder.setCustomSSLContext(context); + return builder; } public static void setEnabledProtocolsAndCiphers(final SSLSocket sslSocket, diff --git a/smack-core/src/test/java/org/jivesoftware/smack/ChatConnectionTest.java b/smack-core/src/test/java/org/jivesoftware/smack/ChatConnectionTest.java index 0476f1c45..9ade29490 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/ChatConnectionTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/ChatConnectionTest.java @@ -354,7 +354,7 @@ public class ChatConnectionTest { try { con.connect(); - con.login("me", "secret"); + con.login(); } catch (Exception e) { // No need for handling in a dummy connection. } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java b/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java index 349e134fb..030f09944 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java @@ -23,6 +23,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PlainStreamElement; import org.jivesoftware.smack.packet.TopLevelStreamElement; @@ -53,8 +54,13 @@ public class DummyConnection extends AbstractXMPPConnection { private final BlockingQueue queue = new LinkedBlockingQueue(); + public static ConnectionConfigurationBuilder getDummyConfigurationBuilder() { + return DummyConnectionConfiguration.builder().setServiceName("example.org").setUsernameAndPassword("dummy", + "dummypass"); + } + public DummyConnection() { - this(new ConnectionConfiguration("example.com")); + this(getDummyConfigurationBuilder().build()); } public DummyConnection(ConnectionConfiguration configuration) { @@ -63,12 +69,16 @@ public class DummyConnection extends AbstractXMPPConnection { for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) { listener.connectionCreated(this); } - connected = true; - user = "dummy@" + config.getServiceName() + "/Test"; + user = config.getUsername() + + "@" + + config.getServiceName() + + "/" + + (config.getResource() != null ? config.getResource() : "Test"); } @Override protected void connectInternal() { + connected = true; connectionID = "dummy-" + new Random(new Date().getTime()).nextInt(); if (reconnect) { @@ -130,19 +140,13 @@ public class DummyConnection extends AbstractXMPPConnection { } @Override - public void login(String username, String password, String resource) + protected void loginNonAnonymously() throws XMPPException { - if (!isConnected()) { - throw new IllegalStateException("Not connected to server."); - } - if (isAuthenticated()) { - throw new IllegalStateException("Already logged in to server."); - } - user = (username != null ? username : "dummy") + user = config.getUsername() + "@" + config.getServiceName() + "/" - + (resource != null ? resource : "Test"); + + (config.getResource() != null ? config.getResource() : "Test"); roster = new Roster(this); anonymous = false; authenticated = true; @@ -226,4 +230,32 @@ public class DummyConnection extends AbstractXMPPConnection { 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 { + + private DummyConnectionConfigurationBuilder() { + } + + @Override + public DummyConnectionConfiguration build() { + return new DummyConnectionConfiguration(this); + } + + @Override + protected DummyConnectionConfigurationBuilder getThis() { + return this; + } + } + } } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/RosterTest.java b/smack-core/src/test/java/org/jivesoftware/smack/RosterTest.java index faebc25ab..a3b1b7dc1 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/RosterTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/RosterTest.java @@ -61,7 +61,7 @@ public class RosterTest { connection = new DummyConnection(); connection.connect(); - connection.login("rostertest", "secret"); + connection.login(); rosterListener = new TestRosterListener(); connection.getRoster().addRosterListener(rosterListener); } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/RosterVersioningTest.java b/smack-core/src/test/java/org/jivesoftware/smack/RosterVersioningTest.java index 6e4980fed..68b70822a 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/RosterVersioningTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/RosterVersioningTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.Collection; import java.util.HashSet; +import org.jivesoftware.smack.ConnectionConfiguration.ConnectionConfigurationBuilder; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.Packet; @@ -64,12 +65,12 @@ public class RosterVersioningTest { DirectoryRosterStore store = DirectoryRosterStore.init(tmpFolder.newFolder("store")); populateStore(store); - ConnectionConfiguration conf = new ConnectionConfiguration("dummy"); - conf.setRosterStore(store); - connection = new DummyConnection(conf); + ConnectionConfigurationBuilder builder = DummyConnection.getDummyConfigurationBuilder(); + builder.setRosterStore(store); + connection = new DummyConnection(builder.build()); connection.connect(); - connection.login("rostertest", "secret"); + connection.login(); } @After diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiatorTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiatorTest.java index 761e41408..d1842748b 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiatorTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiatorTest.java @@ -36,7 +36,7 @@ public class FileTransferNegotiatorTest { connection = new DummyConnection(); connection.connect(); - connection.login("me", "secret"); + connection.login(); ServiceDiscoveryManager.getInstanceFor(connection); } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/iqlast/LastActivityTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/iqlast/LastActivityTest.java index 6f6d95fe8..a9c9221a9 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/iqlast/LastActivityTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/iqlast/LastActivityTest.java @@ -42,6 +42,7 @@ public class LastActivityTest extends InitExtensions { .namespace(LastActivity.NAMESPACE); DummyConnection c = new DummyConnection(); + c.connect(); IQ lastRequest = (IQ) PacketParserUtils.parseStanza(xml.asString()); assertTrue(lastRequest instanceof LastActivity); diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/iqversion/VersionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/iqversion/VersionTest.java index cec0a5bcb..d97ebb28a 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/iqversion/VersionTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/iqversion/VersionTest.java @@ -35,6 +35,7 @@ public class VersionTest { + ""; // @formatter:on DummyConnection con = new DummyConnection(); + con.connect(); // Enable version replys for this connection VersionManager.setAutoAppendSmackVersion(false); diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/ping/PingTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/ping/PingTest.java index 5755b611f..d745b29f3 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/ping/PingTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/ping/PingTest.java @@ -53,7 +53,7 @@ public class PingTest extends InitExtensions { + ""; // @formatter:on DummyConnection con = new DummyConnection(); - + con.connect(); // Enable ping for this connection PingManager.getInstanceFor(con); IQ pingRequest = (IQ) PacketParserUtils.parseStanza(control); @@ -236,7 +236,7 @@ public class PingTest extends InitExtensions { private static ThreadedDummyConnection getAuthentiactedDummyConnection() throws SmackException, IOException, XMPPException { ThreadedDummyConnection connection = new ThreadedDummyConnection(); connection.connect(); - connection.login("foo", "bar"); + connection.login(); return connection; } @@ -252,7 +252,7 @@ public class PingTest extends InitExtensions { DummyConnection con = new DummyConnection(); con.setPacketReplyTimeout(500); con.connect(); - con.login("foo", "bar"); + con.login(); return con; } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java index 72c08130d..3784b8eee 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java @@ -47,7 +47,7 @@ public class ItemValidationTest extends InitExtensions { connection = new ThreadedDummyConnection(); connection.connect(); - connection.login("me", "secret"); + connection.login(); } @After diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java index cb7d6dd13..2eaa031dd 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/receipts/DeliveryReceiptTest.java @@ -70,6 +70,7 @@ public class DeliveryReceiptTest extends InitExtensions { @Test public void receiptManagerListenerTest() throws Exception { DummyConnection c = new DummyConnection(); + c.connect(); // Ensure SDM is created for this connection ServiceDiscoveryManager.getInstanceFor(c); DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c); @@ -101,6 +102,7 @@ public class DeliveryReceiptTest extends InitExtensions { @Test public void receiptManagerAutoReplyTest() throws Exception { DummyConnection c = new DummyConnection(); + c.connect(); // Ensure SDM is created for this connection ServiceDiscoveryManager.getInstanceFor(c); DeliveryReceiptManager drm = DeliveryReceiptManager.getInstanceFor(c); diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index b7666fc7c..0bf850f34 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -86,7 +86,6 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import java.io.BufferedReader; @@ -115,7 +114,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -256,81 +254,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { */ private final Set requestAckPredicates = new LinkedHashSet(); + private final XMPPTCPConnectionConfiguration config; + /** - * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be - * performed to determine the IP address and port corresponding to the - * service name; if that lookup fails, it's assumed that server resides at - * serviceName with the default port of 5222. Encrypted connections (TLS) - * will be used if available, stream compression is disabled, and standard SASL - * mechanisms will be used for authentication.

- *

+ * Creates a new XMPP connection over TCP (optionally using proxies). + *

+ * Note that XMPPTCPConnection constructors do not establish a connection to the server + * and you must call {@link #connect()}. + *

+ * + * @param config the connection configuration. + */ + public XMPPTCPConnection(XMPPTCPConnectionConfiguration config) { + super(config); + this.config = config; + } + + /** + * Creates a new XMPP connection over TCP. + *

* This is the simplest constructor for connecting to an XMPP server. Alternatively, * you can get fine-grained control over connection settings using the - * {@link #XMPPTCPConnection(ConnectionConfiguration)} constructor.

- *

- * Note that XMPPTCPConnection constructors do not establish a connection to the server - * and you must call {@link #connect()}.

- *

- * 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. example.com. - * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. + * {@link #XMPPTCPConnection(XMPPTCPConnectionConfiguration)} constructor. + *

+ * @param username + * @param password + * @param serviceName */ - public XMPPTCPConnection(String serviceName, CallbackHandler callbackHandler) { - // Create the configuration for this new connection - super(new ConnectionConfiguration(serviceName)); - 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. example.com. - */ - 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.

- *

- * 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.

- *

- * Note that XMPPTCPConnection constructors do not establish a connection to the server - * and you must call {@link #connect()}.

- *

- * - * 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); + public XMPPTCPConnection(String username, String password, String serviceName) { + this(XMPPTCPConnectionConfiguration.builder().setUsernameAndPassword(username, password).setServiceName( + serviceName).build()); } @Override @@ -361,19 +314,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } @Override - public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, IOException { - if (!isConnected()) { - throw new NotConnectedException(); + protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException { + packetWriter.throwNotConnectedExceptionIfDoneAndResumptionNotPossible(); + } + + @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(); } + } - // Do partial version of nameprep on the username. - if (username != null) { - username = username.toLowerCase(Locale.US).trim(); - } + @Override + protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException { + // 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()) { // Authenticate using SASL if (password != null) { @@ -396,7 +366,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId)); if (smResumedSyncPoint.wasSuccessful()) { // We successfully resumed the stream, be done here - afterSuccessfulLogin(false, true); + afterSuccessfulLogin(true); return; } // SM resumption failed, what Smack does here is to report success of @@ -435,20 +405,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { sendPacketInternal(stanza); } - // Stores the authentication for future reconnection - setLoginInfo(username, password, resource); - afterSuccessfulLogin(false, false); + afterSuccessfulLogin(false); } @Override 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 saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); @@ -466,7 +427,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { bindResourceAndEstablishSession(null); - afterSuccessfulLogin(true, false); + afterSuccessfulLogin(false); } @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. */ - public void instantShutdown() { + public synchronized void instantShutdown() { shutdown(true); } private void shutdown(boolean instant) { + if (disconnectedButResumeable) { + return; + } if (packetReader != null) { packetReader.shutdown(); } @@ -522,7 +486,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { LOGGER.log(Level.WARNING, "shutdown", e); } - setWasAuthenticated(authenticated); + setWasAuthenticated(); // 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 (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 { try { - maybeResolveDns(); + populateHostAddresses(); } catch (Exception e) { throw new SmackException(e); } - Iterator it = config.getHostAddresses().iterator(); + Iterator it = hostAddresses.iterator(); List failedAddresses = new LinkedList(); while (it.hasNext()) { Exception exception = null; @@ -853,9 +817,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { */ @Override protected void connectInternal() throws SmackException, IOException, XMPPException { - if (connected && !disconnectedButResumeable) { - throw new AlreadyConnectedException(); - } + throwAlreadyConnectedExceptionIfAppropriate(); // Establishes the connection, readers and writers connectUsingConfiguration(config); @@ -869,14 +831,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // Automatically makes the login if the user was previously connected successfully // to the server and the connection was terminated abruptly if (wasAuthenticated) { - // Make the login - if (isAnonymous()) { - // Make the anonymous login - loginAnonymously(); - } - else { - login(config.getUsername(), config.getPassword(), config.getResource()); - } + login(); notifyReconnection(); } } @@ -1052,21 +1007,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { case "stream": // We found an opening stream. if ("jabber:client".equals(parser.getNamespace(null))) { - // Get the connection id. - for (int i=0; i { + 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); + } + } +} diff --git a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java index f7e56611a..338c713e2 100644 --- a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java +++ b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java @@ -45,7 +45,7 @@ public class PacketWriterTest { @SuppressWarnings("javadoc") @Test 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(); connection.packetWriter = pw; connection.packetReader = connection.new PacketReader(); diff --git a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/RosterOfflineTest.java b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/RosterOfflineTest.java index 817c63961..35ffecbcc 100644 --- a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/RosterOfflineTest.java +++ b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/RosterOfflineTest.java @@ -38,7 +38,7 @@ public class RosterOfflineTest { @Before public void setup() throws XMPPException, SmackException { - this.connection = new XMPPTCPConnection("localhost"); + this.connection = new XMPPTCPConnection("user", "pass", "example.org"); assertFalse(connection.isConnected()); roster = connection.getRoster();