/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* 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.openfire.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.cert.Certificate;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.net.VirtualConnection;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.AuthFactory;
import java.util.Locale;
import java.util.concurrent.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.PacketParserUtils;
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import javax.security.auth.callback.*;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmpp.packet.*;
import org.dom4j.*;
public class XMPPConnection extends Connection
{
private static Logger Log = LoggerFactory.getLogger( "XMPPConnection" );
String connectionID;
private String user;
private boolean connected;
private boolean authenticated;
private boolean wasAuthenticated;
private boolean anonymous;
private boolean usingTLS;
Roster roster;
private SSLContext customSslContext;
private boolean usingCompression;
private LocalClientSession session;
OpenfirePacketWriter packetWriter;
OpenfirePacketReader packetReader;
SmackConnection smackConnection;
public XMPPConnection(String serviceName, CallbackHandler callbackHandler)
{
super(new ConnectionConfiguration(serviceName));
connectionID = null;
user = null;
connected = false;
authenticated = false;
wasAuthenticated = false;
anonymous = false;
usingTLS = false;
roster = null;
customSslContext = null;
config.setCompressionEnabled(false);
config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED);
config.setCallbackHandler(callbackHandler);
}
public XMPPConnection(String serviceName)
{
super(new ConnectionConfiguration(serviceName));
connectionID = null;
user = null;
connected = false;
authenticated = false;
wasAuthenticated = false;
anonymous = false;
usingTLS = false;
roster = null;
customSslContext = null;
config.setCompressionEnabled(false);
config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED);
}
public XMPPConnection(ConnectionConfiguration config)
{
super(config);
connectionID = null;
user = null;
connected = false;
authenticated = false;
wasAuthenticated = false;
anonymous = false;
usingTLS = false;
roster = null;
customSslContext = null;
}
public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler)
{
super(config);
connectionID = null;
user = null;
connected = false;
authenticated = false;
wasAuthenticated = false;
anonymous = false;
usingTLS = false;
roster = null;
customSslContext = null;
config.setCallbackHandler(callbackHandler);
}
public String getConnectionID()
{
if(!isConnected())
return null;
else
return connectionID;
}
public String getUser()
{
if(!isAuthenticated())
return null;
else
return user;
}
public void setCustomSslContext(SSLContext customSslContext)
{
this.customSslContext = customSslContext;
}
public synchronized void login(String username, String password, String resource) throws XMPPException
{
Log.info("XMPPConnection login");
if(!isConnected())
throw new IllegalStateException("Not connected to server.");
if(authenticated)
throw new IllegalStateException("Already logged in to server.");
try {
username = username.toLowerCase().trim();
user = username;
config.setServiceName(StringUtils.parseServer("openfire"));
JID userJid = XMPPServer.getInstance().createJID(username, resource);
session = (LocalClientSession) SessionManager.getInstance().getSession(userJid);
if (session != null)
{
session.close();
SessionManager.getInstance().removeSession(session);
}
AuthToken authToken = null;
try {
authToken = AuthFactory.authenticate( username, password );
} catch ( UnauthorizedException e ) {
authToken = new AuthToken(resource, true);
}
session = SessionManager.getInstance().createClientSession( smackConnection, (Locale) null );
smackConnection.setRouter( new SessionPacketRouter( session ) );
session.setAuthToken(authToken, resource);
authenticated = true;
anonymous = false;
if(roster == null)
roster = new Roster(this);
if(config.isRosterLoadedAtLogin())
roster.reload();
packetWriter.sendPacket(new Presence(org.jivesoftware.smack.packet.Presence.Type.available));
config.setLoginInfo(username, password, resource);
if(config.isDebuggerEnabled() && debugger != null) debugger.userHasLogged(user);
} catch (Exception e) {
Log.error("XMPPConnection login error", e);
}
}
public synchronized void loginAnonymously() throws XMPPException
{
loginAnonymousUser("ofmeet-user-" + System.currentTimeMillis());
}
public synchronized void loginAnonymousUser(String userId) throws XMPPException
{
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
if (authenticated) {
throw new IllegalStateException("Already logged in to server.");
}
this.user = userId;
config.setServiceName(StringUtils.parseServer("openfire"));
AuthToken authToken = new AuthToken(this.user, true);
session = SessionManager.getInstance().createClientSession( smackConnection, (Locale) null );
smackConnection.setRouter( new SessionPacketRouter( session ) );
session.setAuthToken(authToken, this.user);
// Set presence to online.
packetWriter.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 Roster getRoster()
{
// synchronize against login()
synchronized(this) {
// if connection is authenticated the roster is already set by login()
// or a previous call to getRoster()
if (!isAuthenticated() || isAnonymous()) {
if (roster == null) {
roster = new Roster(this);
}
return roster;
}
}
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.getPacketReplyTimeout();
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 boolean isConnected()
{
return connected;
}
public boolean isSecureConnection()
{
return isUsingTLS();
}
public boolean isAuthenticated()
{
return authenticated;
}
public boolean isAnonymous()
{
return anonymous;
}
protected void shutdown(Presence unavailablePresence)
{
packetWriter.sendPacket(unavailablePresence);
setWasAuthenticated(authenticated);
authenticated = false;
connected = false;
packetReader.shutdown();
packetWriter.shutdown();
}
public synchronized void disconnect(Presence unavailablePresence)
{
shutdown(unavailablePresence);
if(roster != null)
{
roster.cleanup();
roster = null;
}
wasAuthenticated = false;
packetWriter.cleanup();
packetReader.cleanup();
if (session != null)
{
session.close();
SessionManager.getInstance().removeSession(session);
}
}
public void sendPacket(Packet packet)
{
if(!isConnected())
throw new IllegalStateException("Not connected to server.");
if(packet == null)
{
throw new NullPointerException("Packet is null.");
} else
{
packetWriter.sendPacket(packet);
return;
}
}
/**
* @deprecated Method addPacketWriterInterceptor is deprecated
*/
public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, PacketFilter packetFilter)
{
addPacketInterceptor(packetInterceptor, packetFilter);
}
/**
* @deprecated Method removePacketWriterInterceptor is deprecated
*/
public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor)
{
removePacketInterceptor(packetInterceptor);
}
/**
* @deprecated Method addPacketWriterListener is deprecated
*/
public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter)
{
addPacketSendingListener(packetListener, packetFilter);
}
/**
* @deprecated Method removePacketWriterListener is deprecated
*/
public void removePacketWriterListener(PacketListener packetListener)
{
removePacketSendingListener(packetListener);
}
private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException
{
String host = config.getHost();
int port = config.getPort();
initConnection(host);
}
private void initConnection(String host) throws XMPPException
{
boolean isFirstInitialization = packetReader == null || packetWriter == null;
if (!isFirstInitialization) {
usingCompression = false;
}
try {
if (isFirstInitialization) {
packetWriter = new OpenfirePacketWriter(this);
packetReader = new OpenfirePacketReader(this);
smackConnection = new SmackConnection(host, packetWriter, packetReader);
// If debugging is enabled, we should start the thread that will listen for
// all packets and then log them.
if (config.isDebuggerEnabled()) {
addPacketListener(debugger.getReaderListener(), null);
if (debugger.getWriterListener() != null) {
addPacketSendingListener(debugger.getWriterListener(), null);
}
}
}
else {
packetWriter.init();
packetReader.init();
}
// Start the packet writer. This will open a XMPP stream to the server
packetWriter.startup();
// Start the packet reader. The startup() method will block until we
// get an opening stream packet back from server.
packetReader.startup();
// Make note of the fact that we're now connected.
connected = true;
// Start keep alive process (after TLS was negotiated - if available)
packetWriter.startKeepAliveProcess();
if (isFirstInitialization) {
// Notify listeners that a new connection has been established
for (ConnectionCreationListener listener : getConnectionCreationListeners()) {
listener.connectionCreated(this);
}
}
else if (!wasAuthenticated) {
packetReader.notifyReconnection();
}
}
catch (Exception ex) {
// An exception occurred in setting up the connection. Make sure we shut down the
if (packetWriter != null) {
try {
packetWriter.shutdown();
}
catch (Throwable ignore) { /* ignore */ }
packetWriter = null;
}
if (packetReader != null) {
try {
packetReader.shutdown();
}
catch (Throwable ignore) { /* ignore */ }
packetReader = null;
}
this.setWasAuthenticated(authenticated);
authenticated = false;
connected = false;
throw ex; // Everything stoppped. Now throw the exception.
}
}
public boolean isUsingTLS()
{
return usingTLS;
}
public boolean isUsingCompression()
{
return usingCompression;
}
public void connect() throws XMPPException
{
connectUsingConfiguration(config);
if(connected && wasAuthenticated)
try
{
if(isAnonymous())
loginAnonymously();
else
login(config.getUsername(), config.getPassword(), config.getResource());
}
catch(XMPPException e)
{
e.printStackTrace();
}
}
private void setWasAuthenticated(boolean wasAuthenticated)
{
if(!this.wasAuthenticated)
this.wasAuthenticated = wasAuthenticated;
}
public Socket getSocket()
{
return null;
}
private class OpenfirePacketWriter
{
public XMPPConnection connection;
OpenfirePacketWriter(XMPPConnection connection)
{
this.connection = connection;
init();
}
public void init()
{
}
public void sendPacket(Packet packet)
{
try {
String data = packet.toXML();
Log.debug("OpenfirePacketWriter sendPacket " + data );
smackConnection.getRouter().route(DocumentHelper.parseText(data).getRootElement());
connection.firePacketInterceptors(packet);
connection.firePacketSendingListeners(packet);
} catch ( Exception e ) {
Log.error( "An error occurred while attempting to route the packet : ", e );
}
}
public void startup()
{
}
public void shutdown()
{
}
void cleanup()
{
Log.info("OpenfirePacketWriter cleanup");
connection.interceptors.clear();
connection.sendListeners.clear();
}
public void startKeepAliveProcess()
{
Log.info("OpenfirePacketWriter startKeepAliveProcess");
}
}
private class OpenfirePacketReader
{
public XMPPConnection connection;
private ExecutorService listenerExecutor;
OpenfirePacketReader(XMPPConnection connection)
{
this.connection = connection;
init();
}
public void init()
{
Log.info("OpenfirePacketReader init");
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;
}
});
}
public void startup()
{
Log.info("OpenfirePacketReader startup");
}
public void shutdown()
{
Log.info("OpenfirePacketReader shutdown");
listenerExecutor.shutdown();
}
public void cleanup()
{
Log.info("OpenfirePacketReader cleanup");
connection.interceptors.clear();
connection.sendListeners.clear();
}
public void processPacket(Packet packet)
{
if (packet == null) {
return;
}
Log.debug("OpenfirePacketReader processPacket\n" + packet.toXML());
// 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));
}
public void notifyReconnection()
{
Log.info("OpenfirePacketReader notifyReconnection");
for (ConnectionListener listener : connection.getConnectionListeners())
{
try {
listener.reconnectionSuccessful();
}
catch (Exception e) {
}
}
}
private class ListenerNotification implements Runnable
{
private Packet packet;
public ListenerNotification(Packet packet) {
this.packet = packet;
}
public void run()
{
for (ListenerWrapper listenerWrapper : connection.recvListeners.values())
{
listenerWrapper.notifyListener(packet);
}
}
}
}
public class SmackConnection extends VirtualConnection
{
private SessionPacketRouter router;
private String remoteAddr;
private String hostName;
private LocalClientSession session;
private boolean isSecure = false;
private OpenfirePacketReader reader;
private OpenfirePacketWriter writer;
public SmackConnection(String hostName, OpenfirePacketWriter writer, OpenfirePacketReader reader)
{
this.remoteAddr = hostName;
this.hostName = hostName;
this.reader = reader;
}
public void setReader(OpenfirePacketReader reader) {
this.reader = reader;
}
public void setWriter(OpenfirePacketWriter writer) {
this.writer = writer;
}
public boolean isSecure() {
return isSecure;
}
public void setSecure(boolean isSecure) {
this.isSecure = isSecure;
}
public SessionPacketRouter getRouter()
{
return router;
}
public void setRouter(SessionPacketRouter router)
{
this.router = router;
}
public void closeVirtualConnection()
{
Log.info("SmackConnection - close ");
if (this.reader!= null) this.reader.shutdown();
if (this.writer!= null) this.writer.shutdown();
if (this.reader!= null) this.reader.cleanup();
if (this.writer!= null) this.writer.cleanup();
}
public byte[] getAddress() {
return remoteAddr.getBytes();
}
public String getHostAddress() {
return remoteAddr;
}
public String getHostName() {
return ( hostName != null ) ? hostName : "0.0.0.0";
}
public void systemShutdown() {
}
public void deliver(org.xmpp.packet.Packet packet) throws UnauthorizedException
{
deliverRawText(packet.toXML());
}
public void deliverRawText(String text)
{
Log.debug("SmackConnection - deliverRawText\n" + text);
try {
StringReader stringReader = new StringReader(text);
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(stringReader);
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG)
{
if (parser.getName().equals("message")) {
this.reader.processPacket(PacketParserUtils.parseMessage(parser));
}
else if (parser.getName().equals("iq")) {
this.reader.processPacket(PacketParserUtils.parseIQ(parser, reader.connection));
}
else if (parser.getName().equals("presence")) {
this.reader.processPacket(PacketParserUtils.parsePresence(parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (Exception e) {
Log.error("deliverRawText error", e);
}
}
@Override
public org.jivesoftware.openfire.spi.ConnectionConfiguration getConfiguration()
{
// TODO Here we run into an issue with the ConnectionConfiguration introduced in Openfire 4:
// it is not extensible in the sense that unforeseen connection types can be added.
// For now, null is returned, as this object is likely to be unused (its lifecycle is
// not managed by a ConnectionListener instance).
return null;
}
public Certificate[] getPeerCertificates() {
return null;
}
}
}