mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 20:12:07 +01:00
Add BOSH subproject (SMACK-187)
This commit is contained in:
parent
fea2db2be7
commit
1a94812660
5 changed files with 1069 additions and 3 deletions
121
bosh/src/main/java/org/jivesoftware/smack/BOSHConfiguration.java
Normal file
121
bosh/src/main/java/org/jivesoftware/smack/BOSHConfiguration.java
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2009 Jive Software.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration to use while establishing the connection to the XMPP server via
|
||||||
|
* HTTP binding.
|
||||||
|
*
|
||||||
|
* @see BOSHConnection
|
||||||
|
* @author Guenther Niess
|
||||||
|
*/
|
||||||
|
public class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
|
|
||||||
|
private boolean ssl;
|
||||||
|
private String file;
|
||||||
|
|
||||||
|
public BOSHConfiguration(String xmppDomain) {
|
||||||
|
super(xmppDomain, 7070);
|
||||||
|
setSASLAuthenticationEnabled(true);
|
||||||
|
ssl = false;
|
||||||
|
file = "/http-bind/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public BOSHConfiguration(String xmppDomain, int port) {
|
||||||
|
super(xmppDomain, port);
|
||||||
|
setSASLAuthenticationEnabled(true);
|
||||||
|
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);
|
||||||
|
setSASLAuthenticationEnabled(true);
|
||||||
|
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);
|
||||||
|
setSASLAuthenticationEnabled(true);
|
||||||
|
ssl = https;
|
||||||
|
file = (filePath != null ? filePath : "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProxyEnabled() {
|
||||||
|
return (proxy != null && proxy.getProxyType() != ProxyInfo.ProxyType.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyInfo getProxyInfo() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProxyAddress() {
|
||||||
|
return (proxy != null ? proxy.getProxyAddress() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProxyPort() {
|
||||||
|
return (proxy != null ? proxy.getProxyPort() : 8080);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUsingSSL() {
|
||||||
|
return ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getURI() throws URISyntaxException {
|
||||||
|
if (file.charAt(0) != '/') {
|
||||||
|
file = '/' + file;
|
||||||
|
}
|
||||||
|
return new URI((ssl ? "https://" : "http://") + getHost() + ":" + getPort() + file);
|
||||||
|
}
|
||||||
|
}
|
762
bosh/src/main/java/org/jivesoftware/smack/BOSHConnection.java
Normal file
762
bosh/src/main/java/org/jivesoftware/smack/BOSHConnection.java
Normal file
|
@ -0,0 +1,762 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2009 Jive Software.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PipedReader;
|
||||||
|
import java.io.PipedWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.Connection;
|
||||||
|
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||||
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
|
import org.jivesoftware.smack.PacketCollector;
|
||||||
|
import org.jivesoftware.smack.Roster;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
import org.igniterealtime.jbosh.BOSHClient;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientConfig;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientConnEvent;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientConnListener;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientRequestListener;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientResponseListener;
|
||||||
|
import org.igniterealtime.jbosh.BOSHException;
|
||||||
|
import org.igniterealtime.jbosh.BOSHMessageEvent;
|
||||||
|
import org.igniterealtime.jbosh.BodyQName;
|
||||||
|
import org.igniterealtime.jbosh.ComposableBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a connection to a XMPP server via HTTP binding.
|
||||||
|
* This is specified in the XEP-0206: XMPP Over BOSH.
|
||||||
|
*
|
||||||
|
* @see Connection
|
||||||
|
* @author Guenther Niess
|
||||||
|
*/
|
||||||
|
public class BOSHConnection extends Connection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The XMPP Over Bosh namespace.
|
||||||
|
*/
|
||||||
|
public static final String XMPP_BOSH_NS = "urn:xmpp:xbosh";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BOSH namespace from XEP-0124.
|
||||||
|
*/
|
||||||
|
public static final String BOSH_URI = "http://jabber.org/protocol/httpbind";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The used BOSH client from the jbosh library.
|
||||||
|
*/
|
||||||
|
private BOSHClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the initial configuration used while creating the connection.
|
||||||
|
*/
|
||||||
|
private final BOSHConfiguration config;
|
||||||
|
|
||||||
|
// Some flags which provides some info about the current state.
|
||||||
|
private boolean connected = false;
|
||||||
|
private boolean authenticated = false;
|
||||||
|
private boolean anonymous = false;
|
||||||
|
private boolean isFirstInitialization = true;
|
||||||
|
private boolean wasAuthenticated = false;
|
||||||
|
private boolean done = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Thread environment for sending packet listeners.
|
||||||
|
*/
|
||||||
|
private ExecutorService listenerExecutor;
|
||||||
|
|
||||||
|
// The readerPipe and consumer thread are used for the debugger.
|
||||||
|
private PipedWriter readerPipe;
|
||||||
|
private Thread readerConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BOSH equivalent of the stream ID which is used for DIGEST authentication.
|
||||||
|
*/
|
||||||
|
protected String authID = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The session ID for the BOSH session with the connection manager.
|
||||||
|
*/
|
||||||
|
protected String sessionID = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full JID of the authenticated user.
|
||||||
|
*/
|
||||||
|
private String user = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The roster maybe also called buddy list holds the list of the users contacts.
|
||||||
|
*/
|
||||||
|
private Roster roster = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a HTTP Binding connection to a XMPP server.
|
||||||
|
*
|
||||||
|
* @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 BOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) {
|
||||||
|
super(new BOSHConfiguration(https, host, port, filePath, xmppDomain));
|
||||||
|
this.config = (BOSHConfiguration) getConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a HTTP Binding connection to a XMPP server.
|
||||||
|
*
|
||||||
|
* @param config The configuration which is used for this connection.
|
||||||
|
*/
|
||||||
|
public BOSHConnection(BOSHConfiguration config) {
|
||||||
|
super(config);
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() throws XMPPException {
|
||||||
|
if (connected) {
|
||||||
|
throw new IllegalStateException("Already connected to a server.");
|
||||||
|
}
|
||||||
|
done = false;
|
||||||
|
try {
|
||||||
|
// Ensure a clean starting state
|
||||||
|
if (client != null) {
|
||||||
|
client.close();
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
saslAuthentication.init();
|
||||||
|
sessionID = null;
|
||||||
|
authID = null;
|
||||||
|
|
||||||
|
// Initialize BOSH client
|
||||||
|
BOSHClientConfig.Builder cfgBuilder = BOSHClientConfig.Builder
|
||||||
|
.create(config.getURI(), config.getServiceName());
|
||||||
|
if (config.isProxyEnabled()) {
|
||||||
|
cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort());
|
||||||
|
}
|
||||||
|
client = BOSHClient.create(cfgBuilder.build());
|
||||||
|
|
||||||
|
// Create an executor to deliver incoming packets to listeners.
|
||||||
|
// We'll use a single thread with an unbounded queue.
|
||||||
|
listenerExecutor = Executors
|
||||||
|
.newSingleThreadExecutor(new ThreadFactory() {
|
||||||
|
public Thread newThread(Runnable runnable) {
|
||||||
|
Thread thread = new Thread(runnable,
|
||||||
|
"Smack Listener Processor ("
|
||||||
|
+ connectionCounterValue + ")");
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.addBOSHClientConnListener(new BOSHConnectionListener(this));
|
||||||
|
client.addBOSHClientResponseListener(new BOSHPacketReader(this));
|
||||||
|
|
||||||
|
// Initialize the debugger
|
||||||
|
if (config.isDebuggerEnabled()) {
|
||||||
|
initDebugger();
|
||||||
|
if (isFirstInitialization) {
|
||||||
|
if (debugger.getReaderListener() != null) {
|
||||||
|
addPacketListener(debugger.getReaderListener(), null);
|
||||||
|
}
|
||||||
|
if (debugger.getWriterListener() != null) {
|
||||||
|
addPacketSendingListener(debugger.getWriterListener(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the session creation request
|
||||||
|
client.send(ComposableBody.builder()
|
||||||
|
.setNamespaceDefinition("xmpp", XMPP_BOSH_NS)
|
||||||
|
.setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0")
|
||||||
|
.build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new XMPPException("Can't connect to " + getServiceName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response from the server
|
||||||
|
synchronized (this) {
|
||||||
|
if (!connected) {
|
||||||
|
try {
|
||||||
|
wait(SmackConfiguration.getDefaultPacketReplyTimeout()*6);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no feedback, throw an remote server timeout error
|
||||||
|
if (!connected && !done) {
|
||||||
|
done = true;
|
||||||
|
String errorMessage = "Timeout reached for the connection to "
|
||||||
|
+ getHost() + ":" + getPort() + ".";
|
||||||
|
throw new XMPPException(
|
||||||
|
errorMessage,
|
||||||
|
new XMPPError(XMPPError.Condition.remote_server_timeout, errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectionID() {
|
||||||
|
if (!connected) {
|
||||||
|
return null;
|
||||||
|
} else if (authID != null) {
|
||||||
|
return authID;
|
||||||
|
} else {
|
||||||
|
return sessionID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Roster getRoster() {
|
||||||
|
if (roster == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!config.isRosterLoadedAtLogin()) {
|
||||||
|
roster.reload();
|
||||||
|
}
|
||||||
|
// If this is the first time the user has asked for the roster after calling
|
||||||
|
// login, we want to wait for the server to send back the user's roster.
|
||||||
|
// This behavior shields API users from having to worry about the fact that
|
||||||
|
// roster operations are asynchronous, although they'll still have to listen
|
||||||
|
// for changes to the roster. Note: because of this waiting logic, internal
|
||||||
|
// Smack code should be wary about calling the getRoster method, and may
|
||||||
|
// need to access the roster object directly.
|
||||||
|
if (!roster.rosterInitialized) {
|
||||||
|
try {
|
||||||
|
synchronized (roster) {
|
||||||
|
long waitTime = SmackConfiguration.getDefaultPacketReplyTimeout();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (!roster.rosterInitialized) {
|
||||||
|
if (waitTime <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
roster.wait(waitTime);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
waitTime -= now - start;
|
||||||
|
start = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roster;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnonymous() {
|
||||||
|
return anonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecureConnection() {
|
||||||
|
// TODO: Implement SSL usage
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUsingCompression() {
|
||||||
|
// TODO: Implement compression
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void login(String username, String password, String resource)
|
||||||
|
throws XMPPException {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IllegalStateException("Not connected to server.");
|
||||||
|
}
|
||||||
|
if (authenticated) {
|
||||||
|
throw new IllegalStateException("Already logged in to server.");
|
||||||
|
}
|
||||||
|
// Do partial version of nameprep on the username.
|
||||||
|
username = username.toLowerCase().trim();
|
||||||
|
|
||||||
|
String response;
|
||||||
|
if (config.isSASLAuthenticationEnabled()
|
||||||
|
&& saslAuthentication.hasNonAnonymousAuthentication()) {
|
||||||
|
// Authenticate using SASL
|
||||||
|
if (password != null) {
|
||||||
|
response = saslAuthentication.authenticate(username, password, resource);
|
||||||
|
} else {
|
||||||
|
response = saslAuthentication.authenticate(username, resource, config.getCallbackHandler());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Authenticate using Non-SASL
|
||||||
|
response = new NonSASLAuthentication(this).authenticate(username, password, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the user.
|
||||||
|
if (response != null) {
|
||||||
|
this.user = response;
|
||||||
|
// Update the serviceName with the one returned by the server
|
||||||
|
config.setServiceName(StringUtils.parseServer(response));
|
||||||
|
} else {
|
||||||
|
this.user = username + "@" + getServiceName();
|
||||||
|
if (resource != null) {
|
||||||
|
this.user += "/" + resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the roster if it is not a reconnection.
|
||||||
|
if (this.roster == null) {
|
||||||
|
this.roster = new Roster(this);
|
||||||
|
}
|
||||||
|
if (config.isRosterLoadedAtLogin()) {
|
||||||
|
this.roster.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set presence to online.
|
||||||
|
if (config.isSendPresence()) {
|
||||||
|
sendPacket(new Presence(Presence.Type.available));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that we're now authenticated.
|
||||||
|
authenticated = true;
|
||||||
|
anonymous = false;
|
||||||
|
|
||||||
|
// Stores the autentication for future reconnection
|
||||||
|
config.setLoginInfo(username, password, resource);
|
||||||
|
|
||||||
|
// If debugging is enabled, change the the debug window title to include
|
||||||
|
// the
|
||||||
|
// name we are now logged-in as.l
|
||||||
|
if (config.isDebuggerEnabled() && debugger != null) {
|
||||||
|
debugger.userHasLogged(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loginAnonymously() throws XMPPException {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IllegalStateException("Not connected to server.");
|
||||||
|
}
|
||||||
|
if (authenticated) {
|
||||||
|
throw new IllegalStateException("Already logged in to server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String response;
|
||||||
|
if (config.isSASLAuthenticationEnabled() &&
|
||||||
|
saslAuthentication.hasAnonymousAuthentication()) {
|
||||||
|
response = saslAuthentication.authenticateAnonymously();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Authenticate using Non-SASL
|
||||||
|
response = new NonSASLAuthentication(this).authenticateAnonymously();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the user value.
|
||||||
|
this.user = response;
|
||||||
|
// Update the serviceName with the one returned by the server
|
||||||
|
config.setServiceName(StringUtils.parseServer(response));
|
||||||
|
|
||||||
|
// Anonymous users can't have a roster.
|
||||||
|
roster = null;
|
||||||
|
|
||||||
|
// Set presence to online.
|
||||||
|
if (config.isSendPresence()) {
|
||||||
|
sendPacket(new Presence(Presence.Type.available));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that we're now authenticated.
|
||||||
|
authenticated = true;
|
||||||
|
anonymous = true;
|
||||||
|
|
||||||
|
// If debugging is enabled, change the the debug window title to include the
|
||||||
|
// name we are now logged-in as.
|
||||||
|
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
|
||||||
|
// will be null
|
||||||
|
if (config.isDebuggerEnabled() && debugger != null) {
|
||||||
|
debugger.userHasLogged(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacket(Packet packet) {
|
||||||
|
if (!isConnected()) {
|
||||||
|
throw new IllegalStateException("Not connected to server.");
|
||||||
|
}
|
||||||
|
if (packet == null) {
|
||||||
|
throw new NullPointerException("Packet is null.");
|
||||||
|
}
|
||||||
|
if (!done) {
|
||||||
|
// Invoke interceptors for the new packet that is about to be sent.
|
||||||
|
// Interceptors
|
||||||
|
// may modify the content of the packet.
|
||||||
|
firePacketInterceptors(packet);
|
||||||
|
|
||||||
|
try {
|
||||||
|
send(ComposableBody.builder().setPayloadXML(packet.toXML())
|
||||||
|
.build());
|
||||||
|
} catch (BOSHException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process packet writer listeners. Note that we're using the
|
||||||
|
// sending
|
||||||
|
// thread so it's expected that listeners are fast.
|
||||||
|
firePacketSendingListeners(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect(Presence unavailablePresence) {
|
||||||
|
if (!connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shutdown(unavailablePresence);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
// TODO still needed? Smack 4.0.0 BOSH
|
||||||
|
// if (roster != null) {
|
||||||
|
// roster.cleanup();
|
||||||
|
// roster = null;
|
||||||
|
// }
|
||||||
|
sendListeners.clear();
|
||||||
|
recvListeners.clear();
|
||||||
|
collectors.clear();
|
||||||
|
interceptors.clear();
|
||||||
|
|
||||||
|
// Reset the connection flags
|
||||||
|
wasAuthenticated = false;
|
||||||
|
isFirstInitialization = true;
|
||||||
|
|
||||||
|
// Notify connection listeners of the connection closing if done hasn't already been set.
|
||||||
|
for (ConnectionListener listener : getConnectionListeners()) {
|
||||||
|
try {
|
||||||
|
listener.connectionClosed();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Catch and print any exception so we can recover
|
||||||
|
// from a faulty listener and finish the shutdown process
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connection by setting presence to unavailable and closing the
|
||||||
|
* HTTP client. The shutdown logic will be used during a planned disconnection or when
|
||||||
|
* dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's
|
||||||
|
* BOSH packet reader and {@link Roster} will not be removed; thus
|
||||||
|
* connection's state is kept.
|
||||||
|
*
|
||||||
|
* @param unavailablePresence the presence packet to send during shutdown.
|
||||||
|
*/
|
||||||
|
protected void shutdown(Presence unavailablePresence) {
|
||||||
|
setWasAuthenticated(authenticated);
|
||||||
|
authID = null;
|
||||||
|
sessionID = null;
|
||||||
|
done = true;
|
||||||
|
authenticated = false;
|
||||||
|
connected = false;
|
||||||
|
isFirstInitialization = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.disconnect(ComposableBody.builder()
|
||||||
|
.setNamespaceDefinition("xmpp", XMPP_BOSH_NS)
|
||||||
|
.setPayloadXML(unavailablePresence.toXML())
|
||||||
|
.build());
|
||||||
|
// Wait 150 ms for processes to clean-up, then shutdown.
|
||||||
|
Thread.sleep(150);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close down the readers and writers.
|
||||||
|
if (readerPipe != null) {
|
||||||
|
try {
|
||||||
|
readerPipe.close();
|
||||||
|
}
|
||||||
|
catch (Throwable ignore) { /* ignore */ }
|
||||||
|
reader = null;
|
||||||
|
}
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
catch (Throwable ignore) { /* ignore */ }
|
||||||
|
reader = null;
|
||||||
|
}
|
||||||
|
if (writer != null) {
|
||||||
|
try {
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
catch (Throwable ignore) { /* ignore */ }
|
||||||
|
writer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shut down the listener executor.
|
||||||
|
if (listenerExecutor != null) {
|
||||||
|
listenerExecutor.shutdown();
|
||||||
|
}
|
||||||
|
readerConsumer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the connection has already logged in the server.
|
||||||
|
*
|
||||||
|
* @param wasAuthenticated true if the connection has already been authenticated.
|
||||||
|
*/
|
||||||
|
private void setWasAuthenticated(boolean wasAuthenticated) {
|
||||||
|
if (!this.wasAuthenticated) {
|
||||||
|
this.wasAuthenticated = wasAuthenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a HTTP request to the connection manager with the provided body element.
|
||||||
|
*
|
||||||
|
* @param body the body which will be sent.
|
||||||
|
*/
|
||||||
|
protected void send(ComposableBody body) throws BOSHException {
|
||||||
|
if (!connected) {
|
||||||
|
throw new IllegalStateException("Not connected to a server!");
|
||||||
|
}
|
||||||
|
if (body == null) {
|
||||||
|
throw new NullPointerException("Body mustn't be null!");
|
||||||
|
}
|
||||||
|
if (sessionID != null) {
|
||||||
|
body = body.rebuild().setAttribute(
|
||||||
|
BodyQName.create(BOSH_URI, "sid"), sessionID).build();
|
||||||
|
}
|
||||||
|
client.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a packet after it's been fully parsed by looping through the
|
||||||
|
* installed packet collectors and listeners and letting them examine the
|
||||||
|
* packet to see if they are a match with the filter.
|
||||||
|
*
|
||||||
|
* @param packet the packet to process.
|
||||||
|
*/
|
||||||
|
protected void processPacket(Packet packet) {
|
||||||
|
if (packet == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all collectors and notify the appropriate ones.
|
||||||
|
for (PacketCollector collector : getPacketCollectors()) {
|
||||||
|
collector.processPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliver the incoming packet to listeners.
|
||||||
|
listenerExecutor.submit(new ListenerNotification(packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the SmackDebugger which allows to log and debug XML traffic.
|
||||||
|
*/
|
||||||
|
protected void initDebugger() {
|
||||||
|
// TODO: Maybe we want to extend the SmackDebugger for simplification
|
||||||
|
// and a performance boost.
|
||||||
|
|
||||||
|
// Initialize a empty writer which discards all data.
|
||||||
|
writer = new Writer() {
|
||||||
|
public void write(char[] cbuf, int off, int len) { /* ignore */}
|
||||||
|
public void close() { /* ignore */ }
|
||||||
|
public void flush() { /* ignore */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize a pipe for received raw data.
|
||||||
|
try {
|
||||||
|
readerPipe = new PipedWriter();
|
||||||
|
reader = new PipedReader(readerPipe);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method from the parent class which initializes the debugger.
|
||||||
|
super.initDebugger();
|
||||||
|
|
||||||
|
// Add listeners for the received and sent raw data.
|
||||||
|
client.addBOSHClientResponseListener(new BOSHClientResponseListener() {
|
||||||
|
public void responseReceived(BOSHMessageEvent event) {
|
||||||
|
if (event.getBody() != null) {
|
||||||
|
try {
|
||||||
|
readerPipe.write(event.getBody().toXML());
|
||||||
|
readerPipe.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.addBOSHClientRequestListener(new BOSHClientRequestListener() {
|
||||||
|
public void requestSent(BOSHMessageEvent event) {
|
||||||
|
if (event.getBody() != null) {
|
||||||
|
try {
|
||||||
|
writer.write(event.getBody().toXML());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create and start a thread which discards all read data.
|
||||||
|
readerConsumer = new Thread() {
|
||||||
|
private Thread thread = this;
|
||||||
|
private int bufferLength = 1024;
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
char[] cbuf = new char[bufferLength];
|
||||||
|
while (readerConsumer == thread && !done) {
|
||||||
|
reader.read(cbuf, 0, bufferLength);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
readerConsumer.setDaemon(true);
|
||||||
|
readerConsumer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends out a notification that there was an error with the connection
|
||||||
|
* and closes the connection.
|
||||||
|
*
|
||||||
|
* @param e the exception that causes the connection close event.
|
||||||
|
*/
|
||||||
|
protected void notifyConnectionError(Exception e) {
|
||||||
|
// Closes the connection temporary. A reconnection is possible
|
||||||
|
shutdown(new Presence(Presence.Type.unavailable));
|
||||||
|
// Print the stack trace to help catch the problem
|
||||||
|
e.printStackTrace();
|
||||||
|
// Notify connection listeners of the error.
|
||||||
|
for (ConnectionListener listener : getConnectionListeners()) {
|
||||||
|
try {
|
||||||
|
listener.connectionClosedOnError(e);
|
||||||
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
// Catch and print any exception so we can recover
|
||||||
|
// from a faulty listener
|
||||||
|
e2.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener class which listen for a successfully established connection
|
||||||
|
* and connection errors and notifies the BOSHConnection.
|
||||||
|
*
|
||||||
|
* @author Guenther Niess
|
||||||
|
*/
|
||||||
|
private class BOSHConnectionListener implements BOSHClientConnListener {
|
||||||
|
|
||||||
|
private final BOSHConnection connection;
|
||||||
|
|
||||||
|
public BOSHConnectionListener(BOSHConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the BOSHConnection about connection state changes.
|
||||||
|
* Process the connection listeners and try to login if the
|
||||||
|
* connection was formerly authenticated and is now reconnected.
|
||||||
|
*/
|
||||||
|
public void connectionEvent(BOSHClientConnEvent connEvent) {
|
||||||
|
try {
|
||||||
|
if (connEvent.isConnected()) {
|
||||||
|
connected = true;
|
||||||
|
if (isFirstInitialization) {
|
||||||
|
isFirstInitialization = false;
|
||||||
|
for (ConnectionCreationListener listener : getConnectionCreationListeners()) {
|
||||||
|
listener.connectionCreated(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
if (wasAuthenticated) {
|
||||||
|
connection.login(
|
||||||
|
config.getUsername(),
|
||||||
|
config.getPassword(),
|
||||||
|
config.getResource());
|
||||||
|
}
|
||||||
|
for (ConnectionListener listener : getConnectionListeners()) {
|
||||||
|
listener.reconnectionSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (XMPPException e) {
|
||||||
|
for (ConnectionListener listener : getConnectionListeners()) {
|
||||||
|
listener.reconnectionFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (connEvent.isError()) {
|
||||||
|
try {
|
||||||
|
connEvent.getCause();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
notifyConnectionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
synchronized (connection) {
|
||||||
|
connection.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class notifies all listeners that a packet was received.
|
||||||
|
*/
|
||||||
|
private class ListenerNotification implements Runnable {
|
||||||
|
|
||||||
|
private Packet packet;
|
||||||
|
|
||||||
|
public ListenerNotification(Packet packet) {
|
||||||
|
this.packet = packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
for (ListenerWrapper listenerWrapper : recvListeners.values()) {
|
||||||
|
listenerWrapper.notifyListener(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
bosh/src/main/java/org/jivesoftware/smack/BOSHPacketReader.java
Normal file
165
bosh/src/main/java/org/jivesoftware/smack/BOSHPacketReader.java
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2009 Jive Software.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.xmlpull.mxp1.MXParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.igniterealtime.jbosh.AbstractBody;
|
||||||
|
import org.igniterealtime.jbosh.BOSHClientResponseListener;
|
||||||
|
import org.igniterealtime.jbosh.BOSHMessageEvent;
|
||||||
|
import org.igniterealtime.jbosh.BodyQName;
|
||||||
|
import org.igniterealtime.jbosh.ComposableBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for XML traffic from the BOSH connection manager and parses it into
|
||||||
|
* packet objects.
|
||||||
|
*
|
||||||
|
* @author Guenther Niess
|
||||||
|
*/
|
||||||
|
public class BOSHPacketReader implements BOSHClientResponseListener {
|
||||||
|
|
||||||
|
private BOSHConnection connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a packet reader which listen on a BOSHConnection for received
|
||||||
|
* HTTP responses, parse the packets and notifies the connection.
|
||||||
|
*
|
||||||
|
* @param connection the corresponding connection for the received packets.
|
||||||
|
*/
|
||||||
|
public BOSHPacketReader(BOSHConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the received packets and notify the corresponding connection.
|
||||||
|
*
|
||||||
|
* @param event the BOSH client response which includes the received packet.
|
||||||
|
*/
|
||||||
|
public void responseReceived(BOSHMessageEvent event) {
|
||||||
|
AbstractBody body = event.getBody();
|
||||||
|
if (body != null) {
|
||||||
|
try {
|
||||||
|
if (connection.sessionID == null) {
|
||||||
|
connection.sessionID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "sid"));
|
||||||
|
}
|
||||||
|
if (connection.authID == null) {
|
||||||
|
connection.authID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "authid"));
|
||||||
|
}
|
||||||
|
final XmlPullParser parser = new MXParser();
|
||||||
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
|
||||||
|
true);
|
||||||
|
parser.setInput(new StringReader(body.toXML()));
|
||||||
|
int eventType = parser.getEventType();
|
||||||
|
do {
|
||||||
|
eventType = parser.next();
|
||||||
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
if (parser.getName().equals("body")) {
|
||||||
|
// ignore the container root element
|
||||||
|
} else if (parser.getName().equals("message")) {
|
||||||
|
connection.processPacket(PacketParserUtils.parseMessage(parser));
|
||||||
|
} else if (parser.getName().equals("iq")) {
|
||||||
|
connection.processPacket(PacketParserUtils.parseIQ(parser, connection));
|
||||||
|
} else if (parser.getName().equals("presence")) {
|
||||||
|
connection.processPacket(PacketParserUtils.parsePresence(parser));
|
||||||
|
} else if (parser.getName().equals("challenge")) {
|
||||||
|
// The server is challenging the SASL authentication
|
||||||
|
// made by the client
|
||||||
|
final String challengeData = parser.nextText();
|
||||||
|
connection.getSASLAuthentication()
|
||||||
|
.challengeReceived(challengeData);
|
||||||
|
connection.processPacket(new Challenge(
|
||||||
|
challengeData));
|
||||||
|
} else if (parser.getName().equals("success")) {
|
||||||
|
connection.send(ComposableBody.builder()
|
||||||
|
.setNamespaceDefinition("xmpp", BOSHConnection.XMPP_BOSH_NS)
|
||||||
|
.setAttribute(
|
||||||
|
BodyQName.createWithPrefix(BOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"),
|
||||||
|
"true")
|
||||||
|
.setAttribute(
|
||||||
|
BodyQName.create(BOSHConnection.BOSH_URI, "to"),
|
||||||
|
connection.getServiceName())
|
||||||
|
.build());
|
||||||
|
connection.getSASLAuthentication().authenticated();
|
||||||
|
connection.processPacket(new Success(parser.nextText()));
|
||||||
|
} else if (parser.getName().equals("features")) {
|
||||||
|
parseFeatures(parser);
|
||||||
|
} else if (parser.getName().equals("failure")) {
|
||||||
|
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
|
||||||
|
final Failure failure = PacketParserUtils.parseSASLFailure(parser);
|
||||||
|
connection.getSASLAuthentication().authenticationFailed(failure.getCondition());
|
||||||
|
connection.processPacket(failure);
|
||||||
|
}
|
||||||
|
} else if (parser.getName().equals("error")) {
|
||||||
|
throw new XMPPException(PacketParserUtils.parseStreamError(parser));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
if (connection.isConnected()) {
|
||||||
|
connection.notifyConnectionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and setup the XML stream features.
|
||||||
|
*
|
||||||
|
* @param parser the XML parser, positioned at the start of a message packet.
|
||||||
|
* @throws Exception if an exception occurs while parsing the packet.
|
||||||
|
*/
|
||||||
|
private void parseFeatures(XmlPullParser parser) throws Exception {
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
int eventType = parser.next();
|
||||||
|
|
||||||
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
if (parser.getName().equals("mechanisms")) {
|
||||||
|
// The server is reporting available SASL mechanisms. Store
|
||||||
|
// this information
|
||||||
|
// which will be used later while logging (i.e.
|
||||||
|
// authenticating) into
|
||||||
|
// the server
|
||||||
|
connection.getSASLAuthentication().setAvailableSASLMethods(
|
||||||
|
PacketParserUtils.parseMechanisms(parser));
|
||||||
|
} else if (parser.getName().equals("bind")) {
|
||||||
|
// The server requires the client to bind a resource to the
|
||||||
|
// stream
|
||||||
|
connection.getSASLAuthentication().bindingRequired();
|
||||||
|
} else if (parser.getName().equals("session")) {
|
||||||
|
// The server supports sessions
|
||||||
|
connection.getSASLAuthentication().sessionsSupported();
|
||||||
|
} else if (parser.getName().equals("register")) {
|
||||||
|
connection.getAccountManager().setSupportsAccountCreation(
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
} else if (eventType == XmlPullParser.END_TAG) {
|
||||||
|
if (parser.getName().equals("features")) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
build.gradle
11
build.gradle
|
@ -359,6 +359,17 @@ Warning: This API is beta, outdated and currenlty unmaintained.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project(':bosh') {
|
||||||
|
description = """\
|
||||||
|
Smack BOSH API.
|
||||||
|
This API is considered beta quality.
|
||||||
|
"""
|
||||||
|
dependencies {
|
||||||
|
compile project(':core')
|
||||||
|
compile 'org.igniterealtime.jbosh:jbosh:0.6.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(subprojects - project(':core'))*.jar {
|
(subprojects - project(':core'))*.jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes('Bundle-SymbolicName': project.group + '-' + appendix,
|
attributes('Bundle-SymbolicName': project.group + '-' + appendix,
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
include 'core', 'extensions', 'experimental',
|
include 'core',
|
||||||
'debug', 'resolver-dnsjava', 'resolver-javax',
|
'extensions',
|
||||||
'compression-jzlib', 'legacy', 'jingle'
|
'experimental',
|
||||||
|
'debug',
|
||||||
|
'resolver-dnsjava',
|
||||||
|
'resolver-javax',
|
||||||
|
'compression-jzlib',
|
||||||
|
'legacy',
|
||||||
|
'jingle',
|
||||||
|
'bosh'
|
Loading…
Reference in a new issue