mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-10 18:59:41 +02:00
Created 'tcp' subproject for TCPConnection
Renamed XMPPConnection to TCPConnection, since the other connection type is also called BOSHConnection.
This commit is contained in:
parent
790343867a
commit
a3e64bab18
7 changed files with 32 additions and 22 deletions
|
@ -1,465 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 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 org.jivesoftware.smack.Connection.ListenerWrapper;
|
||||
import org.jivesoftware.smack.packet.*;
|
||||
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
|
||||
import org.jivesoftware.smack.parsing.UnparsablePacket;
|
||||
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.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Listens for XML traffic from the XMPP server and parses it into packet objects.
|
||||
* The packet reader also invokes all packet listeners and collectors.<p>
|
||||
*
|
||||
* @see Connection#createPacketCollector
|
||||
* @see Connection#addPacketListener
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
class PacketReader {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PacketReader.class.getName());
|
||||
|
||||
private Thread readerThread;
|
||||
private ExecutorService listenerExecutor;
|
||||
|
||||
private XMPPConnection connection;
|
||||
private XmlPullParser parser;
|
||||
volatile boolean done;
|
||||
|
||||
private String connectionID = null;
|
||||
|
||||
protected PacketReader(final XMPPConnection connection) {
|
||||
this.connection = connection;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the reader in order to be used. The reader is initialized during the
|
||||
* first connection and when reconnecting due to an abruptly disconnection.
|
||||
*/
|
||||
protected void init() {
|
||||
done = false;
|
||||
connectionID = null;
|
||||
|
||||
readerThread = new Thread() {
|
||||
public void run() {
|
||||
parsePackets(this);
|
||||
}
|
||||
};
|
||||
readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")");
|
||||
readerThread.setDaemon(true);
|
||||
|
||||
// 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 (" + connection.connectionCounterValue + ")");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
|
||||
resetParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the packet reader thread and returns once a connection to the server
|
||||
* has been established. A connection will be attempted for a maximum of five
|
||||
* seconds. An XMPPException will be thrown if the connection fails.
|
||||
*
|
||||
* @throws XMPPException if the server fails to send an opening stream back
|
||||
* for more than five seconds.
|
||||
*/
|
||||
synchronized public void startup() throws XMPPException {
|
||||
readerThread.start();
|
||||
// Wait for stream tag before returning. We'll wait a couple of seconds before
|
||||
// giving up and throwing an error.
|
||||
try {
|
||||
// A waiting thread may be woken up before the wait time or a notify
|
||||
// (although this is a rare thing). Therefore, we continue waiting
|
||||
// until either a connectionID has been set (and hence a notify was
|
||||
// made) or the total wait time has elapsed.
|
||||
int waitTime = SmackConfiguration.getDefaultPacketReplyTimeout();
|
||||
wait(3 * waitTime);
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// Ignore.
|
||||
}
|
||||
if (connectionID == null) {
|
||||
throw new XMPPException("Connection failed. No response from server.");
|
||||
}
|
||||
else {
|
||||
connection.connectionID = connectionID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts the packet reader down.
|
||||
*/
|
||||
public void shutdown() {
|
||||
// Notify connection listeners of the connection closing if done hasn't already been set.
|
||||
if (!done) {
|
||||
for (ConnectionListener listener : connection.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
|
||||
LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
done = true;
|
||||
|
||||
// Shut down the listener executor.
|
||||
listenerExecutor.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the parser using the latest connection's reader. Reseting the parser is necessary
|
||||
* when the plain connection has been secured or when a new opening stream element is going
|
||||
* to be sent by the server.
|
||||
*/
|
||||
private void resetParser() {
|
||||
try {
|
||||
parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
|
||||
parser.setInput(connection.reader);
|
||||
}
|
||||
catch (XmlPullParserException xppe) {
|
||||
LOGGER.log(Level.WARNING, "Error while resetting parser", xppe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse top-level packets in order to process them further.
|
||||
*
|
||||
* @param thread the thread that is being used by the reader to parse incoming packets.
|
||||
*/
|
||||
private void parsePackets(Thread thread) {
|
||||
try {
|
||||
int eventType = parser.getEventType();
|
||||
do {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
int parserDepth = parser.getDepth();
|
||||
ParsingExceptionCallback callback = connection.getParsingExceptionCallback();
|
||||
if (parser.getName().equals("message")) {
|
||||
Packet packet;
|
||||
try {
|
||||
packet = PacketParserUtils.parseMessage(parser);
|
||||
} catch (Exception e) {
|
||||
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
|
||||
UnparsablePacket message = new UnparsablePacket(content, e);
|
||||
if (callback != null) {
|
||||
callback.handleUnparsablePacket(message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
processPacket(packet);
|
||||
}
|
||||
else if (parser.getName().equals("iq")) {
|
||||
IQ iq;
|
||||
try {
|
||||
iq = PacketParserUtils.parseIQ(parser, connection);
|
||||
} catch (Exception e) {
|
||||
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
|
||||
UnparsablePacket message = new UnparsablePacket(content, e);
|
||||
if (callback != null) {
|
||||
callback.handleUnparsablePacket(message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
processPacket(iq);
|
||||
}
|
||||
else if (parser.getName().equals("presence")) {
|
||||
Presence presence;
|
||||
try {
|
||||
presence = PacketParserUtils.parsePresence(parser);
|
||||
} catch (Exception e) {
|
||||
String content = PacketParserUtils.parseContentDepth(parser, parserDepth);
|
||||
UnparsablePacket message = new UnparsablePacket(content, e);
|
||||
if (callback != null) {
|
||||
callback.handleUnparsablePacket(message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
processPacket(presence);
|
||||
}
|
||||
// We found an opening stream. Record information about it, then notify
|
||||
// the connectionID lock so that the packet reader startup can finish.
|
||||
else if (parser.getName().equals("stream")) {
|
||||
// Ensure the correct jabber:client namespace is being used.
|
||||
if ("jabber:client".equals(parser.getNamespace(null))) {
|
||||
// Get the connection id.
|
||||
for (int i=0; i<parser.getAttributeCount(); i++) {
|
||||
if (parser.getAttributeName(i).equals("id")) {
|
||||
// Save the connectionID
|
||||
connectionID = parser.getAttributeValue(i);
|
||||
if (!"1.0".equals(parser.getAttributeValue("", "version"))) {
|
||||
// Notify that a stream has been opened if the
|
||||
// server is not XMPP 1.0 compliant otherwise make the
|
||||
// notification after TLS has been negotiated or if TLS
|
||||
// is not supported
|
||||
releaseConnectionIDLock();
|
||||
}
|
||||
}
|
||||
else if (parser.getAttributeName(i).equals("from")) {
|
||||
// Use the server name that the server says that it is.
|
||||
connection.config.setServiceName(parser.getAttributeValue(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parser.getName().equals("error")) {
|
||||
throw new XMPPException(PacketParserUtils.parseStreamError(parser));
|
||||
}
|
||||
else if (parser.getName().equals("features")) {
|
||||
parseFeatures(parser);
|
||||
}
|
||||
else if (parser.getName().equals("proceed")) {
|
||||
// Secure the connection by negotiating TLS
|
||||
connection.proceedTLSReceived();
|
||||
// Reset the state of the parser since a new stream element is going
|
||||
// to be sent by the server
|
||||
resetParser();
|
||||
}
|
||||
else if (parser.getName().equals("failure")) {
|
||||
String namespace = parser.getNamespace(null);
|
||||
if ("urn:ietf:params:xml:ns:xmpp-tls".equals(namespace)) {
|
||||
// TLS negotiation has failed. The server will close the connection
|
||||
throw new Exception("TLS negotiation has failed");
|
||||
}
|
||||
else if ("http://jabber.org/protocol/compress".equals(namespace)) {
|
||||
// Stream compression has been denied. This is a recoverable
|
||||
// situation. It is still possible to authenticate and
|
||||
// use the connection but using an uncompressed connection
|
||||
connection.streamCompressionDenied();
|
||||
}
|
||||
else {
|
||||
// SASL authentication has failed. The server may close the connection
|
||||
// depending on the number of retries
|
||||
final Failure failure = PacketParserUtils.parseSASLFailure(parser);
|
||||
processPacket(failure);
|
||||
connection.getSASLAuthentication().authenticationFailed(failure.getCondition());
|
||||
}
|
||||
}
|
||||
else if (parser.getName().equals("challenge")) {
|
||||
// The server is challenging the SASL authentication made by the client
|
||||
String challengeData = parser.nextText();
|
||||
processPacket(new Challenge(challengeData));
|
||||
connection.getSASLAuthentication().challengeReceived(challengeData);
|
||||
}
|
||||
else if (parser.getName().equals("success")) {
|
||||
processPacket(new Success(parser.nextText()));
|
||||
// We now need to bind a resource for the connection
|
||||
// Open a new stream and wait for the response
|
||||
connection.packetWriter.openStream();
|
||||
// Reset the state of the parser since a new stream element is going
|
||||
// to be sent by the server
|
||||
resetParser();
|
||||
// The SASL authentication with the server was successful. The next step
|
||||
// will be to bind the resource
|
||||
connection.getSASLAuthentication().authenticated();
|
||||
}
|
||||
else if (parser.getName().equals("compressed")) {
|
||||
// Server confirmed that it's possible to use stream compression. Start
|
||||
// stream compression
|
||||
connection.startStreamCompression();
|
||||
// Reset the state of the parser since a new stream element is going
|
||||
// to be sent by the server
|
||||
resetParser();
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("stream")) {
|
||||
// Disconnect the connection
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
} while (!done && eventType != XmlPullParser.END_DOCUMENT && thread == readerThread);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// The exception can be ignored if the the connection is 'done'
|
||||
// or if the it was caused because the socket got closed
|
||||
if (!(done || connection.isSocketClosed())) {
|
||||
// Close the connection and notify connection listeners of the
|
||||
// error.
|
||||
connection.notifyConnectionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the connection ID lock so that the thread that was waiting can resume. The
|
||||
* lock will be released when one of the following three conditions is met:<p>
|
||||
*
|
||||
* 1) An opening stream was sent from a non XMPP 1.0 compliant server
|
||||
* 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS
|
||||
* 3) TLS negotiation was successful
|
||||
*
|
||||
*/
|
||||
synchronized private void releaseConnectionIDLock() {
|
||||
notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private void processPacket(Packet packet) {
|
||||
if (packet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through all collectors and notify the appropriate ones.
|
||||
for (PacketCollector collector: connection.getPacketCollectors()) {
|
||||
collector.processPacket(packet);
|
||||
}
|
||||
|
||||
// Deliver the incoming packet to listeners.
|
||||
listenerExecutor.submit(new ListenerNotification(packet));
|
||||
}
|
||||
|
||||
private void parseFeatures(XmlPullParser parser) throws Exception {
|
||||
boolean startTLSReceived = false;
|
||||
boolean startTLSRequired = false;
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
int eventType = parser.next();
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (parser.getName().equals("starttls")) {
|
||||
startTLSReceived = true;
|
||||
}
|
||||
else 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();
|
||||
}
|
||||
// Set the entity caps node for the server if one is send
|
||||
// See http://xmpp.org/extensions/xep-0115.html#stream
|
||||
else if (parser.getName().equals("c")) {
|
||||
String node = parser.getAttributeValue(null, "node");
|
||||
String ver = parser.getAttributeValue(null, "ver");
|
||||
if (ver != null && node != null) {
|
||||
String capsNode = node + "#" + ver;
|
||||
// In order to avoid a dependency from smack to smackx
|
||||
// we have to set the services caps node in the connection
|
||||
// and not directly in the EntityCapsManager
|
||||
connection.setServiceCapsNode(capsNode);
|
||||
}
|
||||
}
|
||||
else if (parser.getName().equals("session")) {
|
||||
// The server supports sessions
|
||||
connection.getSASLAuthentication().sessionsSupported();
|
||||
}
|
||||
else if (parser.getName().equals("ver")) {
|
||||
if (parser.getNamespace().equals("urn:xmpp:features:rosterver")) {
|
||||
connection.setRosterVersioningSupported();
|
||||
}
|
||||
}
|
||||
else if (parser.getName().equals("compression")) {
|
||||
// The server supports stream compression
|
||||
connection.setAvailableCompressionMethods(PacketParserUtils.parseCompressionMethods(parser));
|
||||
}
|
||||
else if (parser.getName().equals("register")) {
|
||||
connection.getAccountManager().setSupportsAccountCreation(true);
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("starttls")) {
|
||||
// Confirm the server that we want to use TLS
|
||||
connection.startTLSReceived(startTLSRequired);
|
||||
}
|
||||
else if (parser.getName().equals("required") && startTLSReceived) {
|
||||
startTLSRequired = true;
|
||||
}
|
||||
else if (parser.getName().equals("features")) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If TLS is required but the server doesn't offer it, disconnect
|
||||
// from the server and throw an error. First check if we've already negotiated TLS
|
||||
// and are secure, however (features get parsed a second time after TLS is established).
|
||||
if (!connection.isSecureConnection()) {
|
||||
if (!startTLSReceived && connection.getConfiguration().getSecurityMode() ==
|
||||
ConnectionConfiguration.SecurityMode.required)
|
||||
{
|
||||
throw new XMPPException("Server does not support security (TLS), " +
|
||||
"but security required by connection configuration.",
|
||||
new XMPPError(XMPPError.Condition.forbidden));
|
||||
}
|
||||
}
|
||||
|
||||
// Release the lock after TLS has been negotiated or we are not insterested in TLS
|
||||
if (!startTLSReceived || connection.getConfiguration().getSecurityMode() ==
|
||||
ConnectionConfiguration.SecurityMode.disabled)
|
||||
{
|
||||
releaseConnectionIDLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A runnable to notify all listeners of a packet.
|
||||
*/
|
||||
private class ListenerNotification implements Runnable {
|
||||
|
||||
private Packet packet;
|
||||
|
||||
public ListenerNotification(Packet packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) {
|
||||
try {
|
||||
listenerWrapper.notifyListener(packet);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception in packet listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 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 org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Writes packets to a XMPP server. Packets are sent using a dedicated thread. Packet
|
||||
* interceptors can be registered to dynamically modify packets before they're actually
|
||||
* sent. Packet listeners can be registered to listen for all outgoing packets.
|
||||
*
|
||||
* @see Connection#addPacketInterceptor
|
||||
* @see Connection#addPacketSendingListener
|
||||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
class PacketWriter {
|
||||
private static final Logger LOGGER = Logger.getLogger(PacketWriter.class.getName());
|
||||
|
||||
private Thread writerThread;
|
||||
private Writer writer;
|
||||
private XMPPConnection connection;
|
||||
private final BlockingQueue<Packet> queue;
|
||||
volatile boolean done;
|
||||
|
||||
/**
|
||||
* Creates a new packet writer with the specified connection.
|
||||
*
|
||||
* @param connection the connection.
|
||||
*/
|
||||
protected PacketWriter(XMPPConnection connection) {
|
||||
this.queue = new ArrayBlockingQueue<Packet>(500, true);
|
||||
this.connection = connection;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the writer in order to be used. It is called at the first connection and also
|
||||
* is invoked if the connection is disconnected by an error.
|
||||
*/
|
||||
protected void init() {
|
||||
this.writer = connection.writer;
|
||||
done = false;
|
||||
|
||||
writerThread = new Thread() {
|
||||
public void run() {
|
||||
writePackets(this);
|
||||
}
|
||||
};
|
||||
writerThread.setName("Smack Packet Writer (" + connection.connectionCounterValue + ")");
|
||||
writerThread.setDaemon(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the specified packet to the server.
|
||||
*
|
||||
* @param packet the packet to send.
|
||||
*/
|
||||
public void sendPacket(Packet packet) {
|
||||
if (!done) {
|
||||
// Invoke interceptors for the new packet that is about to be sent. Interceptors
|
||||
// may modify the content of the packet.
|
||||
connection.firePacketInterceptors(packet);
|
||||
|
||||
try {
|
||||
queue.put(packet);
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to queue packet to send to server: " + packet.toString(), ie);
|
||||
return;
|
||||
}
|
||||
synchronized (queue) {
|
||||
queue.notifyAll();
|
||||
}
|
||||
|
||||
// Process packet writer listeners. Note that we're using the sending
|
||||
// thread so it's expected that listeners are fast.
|
||||
connection.firePacketSendingListeners(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the packet writer thread and opens a connection to the server. The
|
||||
* packet writer will continue writing packets until {@link #shutdown} or an
|
||||
* error occurs.
|
||||
*/
|
||||
public void startup() {
|
||||
writerThread.start();
|
||||
}
|
||||
|
||||
void setWriter(Writer writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the packet writer. Once this method has been called, no further
|
||||
* packets will be written to the server.
|
||||
*/
|
||||
public void shutdown() {
|
||||
done = true;
|
||||
synchronized (queue) {
|
||||
queue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next available packet from the queue for writing.
|
||||
*
|
||||
* @return the next packet for writing.
|
||||
*/
|
||||
private Packet nextPacket() {
|
||||
Packet packet = null;
|
||||
// Wait until there's a packet or we're done.
|
||||
while (!done && (packet = queue.poll()) == null) {
|
||||
try {
|
||||
synchronized (queue) {
|
||||
queue.wait();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void writePackets(Thread thisThread) {
|
||||
try {
|
||||
// Open the stream.
|
||||
openStream();
|
||||
// Write out packets from the queue.
|
||||
while (!done && (writerThread == thisThread)) {
|
||||
Packet packet = nextPacket();
|
||||
if (packet != null) {
|
||||
writer.write(packet.toXML());
|
||||
|
||||
if (queue.isEmpty()) {
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Flush out the rest of the queue. If the queue is extremely large, it's possible
|
||||
// we won't have time to entirely flush it before the socket is forced closed
|
||||
// by the shutdown process.
|
||||
try {
|
||||
while (!queue.isEmpty()) {
|
||||
Packet packet = queue.remove();
|
||||
writer.write(packet.toXML());
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.warning("Error flushing queue during shutdown, ignore and continue");
|
||||
}
|
||||
|
||||
// Delete the queue contents (hopefully nothing is left).
|
||||
queue.clear();
|
||||
|
||||
// Close the stream.
|
||||
try {
|
||||
writer.write("</stream:stream>");
|
||||
writer.flush();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Do nothing
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
writer.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
// The exception can be ignored if the the connection is 'done'
|
||||
// or if the it was caused because the socket got closed
|
||||
if (!(done || connection.isSocketClosed())) {
|
||||
done = true;
|
||||
// packetReader could be set to null by an concurrent disconnect() call.
|
||||
// Therefore Prevent NPE exceptions by checking packetReader.
|
||||
if (connection.packetReader != null) {
|
||||
connection.notifyConnectionError(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends to the server a new stream element. This operation may be requested several times
|
||||
* so we need to encapsulate the logic in one place. This message will be sent while doing
|
||||
* TLS, SASL and resource binding.
|
||||
*
|
||||
* @throws IOException If an error occurs while sending the stanza to the server.
|
||||
*/
|
||||
void openStream() throws IOException {
|
||||
StringBuilder stream = new StringBuilder();
|
||||
stream.append("<stream:stream");
|
||||
stream.append(" to=\"").append(connection.getServiceName()).append("\"");
|
||||
stream.append(" xmlns=\"jabber:client\"");
|
||||
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
|
||||
stream.append(" version=\"1.0\">");
|
||||
writer.write(stream.toString());
|
||||
writer.flush();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,108 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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 static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.jivesoftware.smack.Roster.SubscriptionMode;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests the behavior of the roster if the connection is not authenticated yet.
|
||||
*
|
||||
* @author Henning Staib
|
||||
*/
|
||||
public class RosterOfflineTest {
|
||||
|
||||
Connection connection;
|
||||
|
||||
Roster roster;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.connection = new XMPPConnection("localhost");
|
||||
assertFalse(connection.isConnected());
|
||||
|
||||
roster = connection.getRoster();
|
||||
assertNotNull(roster);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowNoExceptionOnGetterMethods() {
|
||||
// all getter methods should work
|
||||
assertFalse(roster.contains("test"));
|
||||
|
||||
Collection<RosterEntry> entries = roster.getEntries();
|
||||
assertTrue(entries.size() == 0);
|
||||
|
||||
assertNull(roster.getEntry("test"));
|
||||
|
||||
assertEquals(0, roster.getEntryCount());
|
||||
|
||||
assertNull(roster.getGroup("test"));
|
||||
|
||||
assertEquals(0, roster.getGroupCount());
|
||||
|
||||
Collection<RosterGroup> groups = roster.getGroups();
|
||||
assertEquals(0, groups.size());
|
||||
|
||||
Presence presence = roster.getPresence("test");
|
||||
assertEquals(Presence.Type.unavailable, presence.getType());
|
||||
|
||||
Presence presenceResource = roster.getPresenceResource("test");
|
||||
assertEquals(Presence.Type.unavailable, presenceResource.getType());
|
||||
|
||||
Iterator<Presence> iterator = roster.getPresences("test");
|
||||
assertTrue(iterator.hasNext());
|
||||
assertEquals(Presence.Type.unavailable, iterator.next().getType());
|
||||
assertFalse(iterator.hasNext());
|
||||
|
||||
assertEquals(0, roster.getUnfiledEntries().size());
|
||||
|
||||
assertEquals(0, roster.getUnfiledEntryCount());
|
||||
|
||||
roster.setSubscriptionMode(SubscriptionMode.accept_all);
|
||||
assertEquals(SubscriptionMode.accept_all, roster.getSubscriptionMode());
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowExceptionOnCreateEntry() throws Exception {
|
||||
roster.createEntry("test", "test", null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowExceptionOnCreateGroup() throws Exception {
|
||||
roster.createGroup("test");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowExceptionOnReload() throws Exception {
|
||||
roster.reload();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowExceptionRemoveEntry() throws Exception {
|
||||
roster.removeEntry(null);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue