KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > smack > XMPPConnection


1 /**
2  * $RCSfile$
3  * $Revision: 2733 $
4  * $Date: 2005-08-26 23:33:08 -0300 (Fri, 26 Aug 2005) $
5  *
6  * Copyright 2003-2004 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */

20
21 package org.jivesoftware.smack;
22
23 import org.jivesoftware.smack.debugger.SmackDebugger;
24 import org.jivesoftware.smack.filter.PacketFilter;
25 import org.jivesoftware.smack.filter.PacketIDFilter;
26 import org.jivesoftware.smack.packet.*;
27 import org.jivesoftware.smack.util.StringUtils;
28
29 import javax.net.SocketFactory;
30 import javax.net.ssl.SSLContext;
31 import javax.net.ssl.SSLSocket;
32 import java.io.*;
33 import java.lang.reflect.Constructor JavaDoc;
34 import java.net.Socket JavaDoc;
35 import java.net.UnknownHostException JavaDoc;
36 import java.util.ArrayList JavaDoc;
37 import java.util.List JavaDoc;
38
39 /**
40  * Creates a connection to a XMPP server. A simple use of this API might
41  * look like the following:
42  * <pre>
43  * // Create a connection to the jivesoftware.com XMPP server.
44  * XMPPConnection con = new XMPPConnection("jivesoftware.com");
45  * // Most servers require you to login before performing other tasks.
46  * con.login("jsmith", "mypass");
47  * // Start a new conversation with John Doe and send him a message.
48  * Chat chat = con.createChat("jdoe@jabber.org");
49  * chat.sendMessage("Hey, how's it going?");
50  * </pre>
51  *
52  * @author Matt Tucker
53  */

54 public class XMPPConnection {
55
56     /**
57      * Value that indicates whether debugging is enabled. When enabled, a debug
58      * window will apear for each new connection that will contain the following
59      * information:<ul>
60      * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
61      * <li> Server Traffic -- raw XML traffic sent by the server to the client.
62      * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
63      * </ul>
64      *
65      * Debugging can be enabled by setting this field to true, or by setting the Java system
66      * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
67      * command line such as "java SomeApp -Dsmack.debugEnabled=true".
68      */

69     public static boolean DEBUG_ENABLED = false;
70
71     private static List JavaDoc connectionEstablishedListeners = new ArrayList JavaDoc();
72
73     static {
74         // Use try block since we may not have permission to get a system
75
// property (for example, when an applet).
76
try {
77             DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
78         }
79         catch (Exception JavaDoc e) {
80         }
81         // Ensure the SmackConfiguration class is loaded by calling a method in it.
82
SmackConfiguration.getVersion();
83     }
84     private SmackDebugger debugger = null;
85
86     /**
87      * IP address or host name of the server. This information is only used when
88      * creating new socket connections to the server. If this information is not
89      * configured then it will be assumed that the host name matches the service name.
90      */

91     String JavaDoc host;
92     int port;
93     Socket socket;
94     /**
95      * Hostname of the XMPP server. Usually servers use the same service name as the name
96      * of the server. However, there are some servers like google where host would be
97      * talk.google.com and the serviceName would be gmail.com.
98      */

99     String JavaDoc serviceName;
100
101     String JavaDoc connectionID;
102     private String JavaDoc user = null;
103     private boolean connected = false;
104     private boolean authenticated = false;
105     private boolean anonymous = false;
106     private boolean usingTLS = false;
107
108     PacketWriter packetWriter;
109     PacketReader packetReader;
110
111     Roster roster = null;
112     private AccountManager accountManager = null;
113     private SASLAuthentication saslAuthentication = new SASLAuthentication(this);
114
115     Writer writer;
116     Reader reader;
117
118     /**
119      * Creates a new connection to the specified XMPP server. The default port of 5222 will
120      * be used. The IP address of the server is assumed to match the service name.
121      *
122      * @param serviceName the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
123      * @throws XMPPException if an error occurs while trying to establish the connection.
124      * Two possible errors can occur which will be wrapped by an XMPPException --
125      * UnknownHostException (XMPP error code 504), and IOException (XMPP error code
126      * 502). The error codes and wrapped exceptions can be used to present more
127      * appropiate error messages to end-users.
128      */

129     public XMPPConnection(String JavaDoc serviceName) throws XMPPException {
130         this(serviceName, 5222, serviceName);
131     }
132
133     /**
134      * Creates a new connection to the specified XMPP server on the given port. The IP address
135      * of the server is assumed to match the service name.
136      *
137      * @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
138      * @param port the port on the server that should be used; e.g. <tt>5222</tt>.
139      * @throws XMPPException if an error occurs while trying to establish the connection.
140      * Two possible errors can occur which will be wrapped by an XMPPException --
141      * UnknownHostException (XMPP error code 504), and IOException (XMPP error code
142      * 502). The error codes and wrapped exceptions can be used to present more
143      * appropiate error messages to end-users.
144      */

145     public XMPPConnection(String JavaDoc host, int port) throws XMPPException {
146         this(host, port, host);
147     }
148
149     /**
150      * Creates a new connection to the specified XMPP server on the given host and port.
151      *
152      * @param host the host name, or null for the loopback address.
153      * @param port the port on the server that should be used; e.g. <tt>5222</tt>.
154      * @param serviceName the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
155      * @throws XMPPException if an error occurs while trying to establish the connection.
156      * Two possible errors can occur which will be wrapped by an XMPPException --
157      * UnknownHostException (XMPP error code 504), and IOException (XMPP error code
158      * 502). The error codes and wrapped exceptions can be used to present more
159      * appropiate error messages to end-users.
160      */

161     public XMPPConnection(String JavaDoc host, int port, String JavaDoc serviceName) throws XMPPException {
162         this.host = host;
163         this.port = port;
164         try {
165             this.socket = new Socket(host, port);
166         }
167         catch (UnknownHostException JavaDoc uhe) {
168             throw new XMPPException(
169                 "Could not connect to " + host + ":" + port + ".",
170                 new XMPPError(504),
171                 uhe);
172         }
173         catch (IOException ioe) {
174             throw new XMPPException(
175                 "XMPPError connecting to " + host + ":" + port + ".",
176                 new XMPPError(502),
177                 ioe);
178         }
179         this.serviceName = serviceName;
180         init();
181     }
182
183     /**
184      * Creates a new connection to the specified XMPP server on the given port using the
185      * specified SocketFactory.<p>
186      *
187      * A custom SocketFactory allows fine-grained control of the actual connection to the
188      * XMPP server. A typical use for a custom SocketFactory is when connecting through a
189      * SOCKS proxy.
190      *
191      * @param host the host name, or null for the loopback address.
192      * @param port the port on the server that should be used; e.g. <tt>5222</tt>.
193      * @param serviceName the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
194      * @param socketFactory a SocketFactory that will be used to create the socket to the XMPP
195      * server.
196      * @throws XMPPException if an error occurs while trying to establish the connection.
197      * Two possible errors can occur which will be wrapped by an XMPPException --
198      * UnknownHostException (XMPP error code 504), and IOException (XMPP error code
199      * 502). The error codes and wrapped exceptions can be used to present more
200      * appropiate error messages to end-users.
201      */

202     public XMPPConnection(String JavaDoc host, int port, String JavaDoc serviceName, SocketFactory socketFactory)
203             throws XMPPException {
204         this.host = host;
205         this.port = port;
206         try {
207             this.socket = socketFactory.createSocket(host, port);
208         }
209         catch (UnknownHostException JavaDoc uhe) {
210             throw new XMPPException(
211                 "Could not connect to " + host + ":" + port + ".",
212                 new XMPPError(504),
213                 uhe);
214         }
215         catch (IOException ioe) {
216             throw new XMPPException(
217                 "XMPPError connecting to " + host + ":" + port + ".",
218                 new XMPPError(502),
219                 ioe);
220         }
221         this.serviceName = serviceName;
222         init();
223     }
224
225     /**
226      * Package-private default constructor. This constructor is only intended
227      * for unit testing. Normal classes extending XMPPConnection should override
228      * one of the other constructors.
229      */

230     XMPPConnection() {
231     }
232
233     /**
234      * Returns the connection ID for this connection, which is the value set by the server
235      * when opening a XMPP stream. If the server does not set a connection ID, this value
236      * will be null.
237      *
238      * @return the ID of this connection returned from the XMPP server.
239      */

240     public String JavaDoc getConnectionID() {
241         return connectionID;
242     }
243
244     /**
245      * Returns the name of the service provided by the XMPP server for this connection. After
246      * authenticating with the server the returned value may be different.
247      *
248      * @return the name of the service provided by the XMPP server.
249      */

250     public String JavaDoc getServiceName() {
251         return serviceName;
252     }
253
254     /**
255      * Returns the host name of the server where the XMPP server is running. This would be the
256      * IP address of the server or a name that may be resolved by a DNS server.
257      *
258      * @return the host name of the server where the XMPP server is running.
259      */

260     public String JavaDoc getHost() {
261         return host;
262     }
263
264     /**
265      * Returns the port number of the XMPP server for this connection. The default port
266      * for normal connections is 5222. The default port for SSL connections is 5223.
267      *
268      * @return the port number of the XMPP server.
269      */

270     public int getPort() {
271         return port;
272     }
273
274     /**
275      * Returns the full XMPP address of the user that is logged in to the connection or
276      * <tt>null</tt> if not logged in yet. An XMPP address is in the form
277      * username@server/resource.
278      *
279      * @return the full XMPP address of the user logged in.
280      */

281     public String JavaDoc getUser() {
282         if (!isAuthenticated()) {
283             return null;
284         }
285         return user;
286     }
287
288     /**
289      * Logs in to the server using the strongest authentication mode supported by
290      * the server, then set our presence to available. If more than five seconds
291      * (default timeout) elapses in each step of the authentication process without
292      * a response from the server, or if an error occurs, a XMPPException will be thrown.
293      *
294      * @param username the username.
295      * @param password the password.
296      * @throws XMPPException if an error occurs.
297      */

298     public void login(String JavaDoc username, String JavaDoc password) throws XMPPException {
299         login(username, password, "Smack");
300     }
301
302     /**
303      * Logs in to the server using the strongest authentication mode supported by
304      * the server, then sets presence to available. If more than five seconds
305      * (default timeout) elapses in each step of the authentication process without
306      * a response from the server, or if an error occurs, a XMPPException will be thrown.
307      *
308      * @param username the username.
309      * @param password the password.
310      * @param resource the resource.
311      * @throws XMPPException if an error occurs.
312      * @throws IllegalStateException if not connected to the server, or already logged in
313      * to the serrver.
314      */

315     public synchronized void login(String JavaDoc username, String JavaDoc password, String JavaDoc resource)
316             throws XMPPException
317     {
318         login(username, password, resource, true);
319     }
320
321     /**
322      * Logs in to the server using the strongest authentication mode supported by
323      * the server. An available presence may optionally be sent. If <tt>sendPresence</tt>
324      * is false, a presence packet must be sent manually later. If more than five seconds
325      * (default timeout) elapses in each step of the authentication process without a
326      * response from the server, or if an error occurs, a XMPPException will be thrown.
327      *
328      * @param username the username.
329      * @param password the password.
330      * @param resource the resource.
331      * @param sendPresence if <tt>true</tt> an available presence will be sent automatically
332      * after login is completed.
333      * @throws XMPPException if an error occurs.
334      * @throws IllegalStateException if not connected to the server, or already logged in
335      * to the serrver.
336      */

337     public synchronized void login(String JavaDoc username, String JavaDoc password, String JavaDoc resource,
338             boolean sendPresence) throws XMPPException
339     {
340         if (!isConnected()) {
341             throw new IllegalStateException JavaDoc("Not connected to server.");
342         }
343         if (authenticated) {
344             throw new IllegalStateException JavaDoc("Already logged in to server.");
345         }
346         // Do partial version of nameprep on the username.
347
username = username.toLowerCase().trim();
348
349         String JavaDoc response = null;
350         if (usingTLS) {
351             // Authenticate using SASL
352
response = saslAuthentication.authenticate(username, password, resource);
353         }
354         else {
355             // Authenticate using Non-SASL
356
response = new NonSASLAuthentication(this).authenticate(username, password, resource);
357         }
358
359         // Set the user.
360
if (response != null) {
361             this.user = response;
362             // Update the serviceName with the one returned by the server
363
this.serviceName = StringUtils.parseServer(response);
364         }
365         else {
366             this.user = username + "@" + this.serviceName;
367             if (resource != null) {
368                 this.user += "/" + resource;
369             }
370         }
371
372         // Create the roster.
373
this.roster = new Roster(this);
374         roster.reload();
375
376         // Set presence to online.
377
if (sendPresence) {
378             packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
379         }
380
381         // Indicate that we're now authenticated.
382
authenticated = true;
383         anonymous = false;
384
385         // If debugging is enabled, change the the debug window title to include the
386
// name we are now logged-in as.
387
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
388
// will be null
389
if (DEBUG_ENABLED && debugger != null) {
390             debugger.userHasLogged(user);
391         }
392     }
393
394     /**
395      * Logs in to the server anonymously. Very few servers are configured to support anonymous
396      * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login
397      * does succeed, your XMPP address will likely be in the form "server/123ABC" (where "123ABC"
398      * is a random value generated by the server).
399      *
400      * @throws XMPPException if an error occurs or anonymous logins are not supported by the server.
401      * @throws IllegalStateException if not connected to the server, or already logged in
402      * to the serrver.
403      */

404     public synchronized void loginAnonymously() throws XMPPException {
405         if (!isConnected()) {
406             throw new IllegalStateException JavaDoc("Not connected to server.");
407         }
408         if (authenticated) {
409             throw new IllegalStateException JavaDoc("Already logged in to server.");
410         }
411
412         // Create the authentication packet we'll send to the server.
413
Authentication auth = new Authentication();
414
415         PacketCollector collector =
416             packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID()));
417         // Send the packet.
418
packetWriter.sendPacket(auth);
419         // Wait up to a certain number of seconds for a response from the server.
420
IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
421         if (response == null) {
422             throw new XMPPException("Anonymous login failed.");
423         }
424         else if (response.getType() == IQ.Type.ERROR) {
425             throw new XMPPException(response.getError());
426         }
427         // Set the user value.
428
if (response.getTo() != null) {
429             this.user = response.getTo();
430         }
431         else {
432             this.user = this.serviceName + "/" + ((Authentication) response).getResource();
433         }
434         // We're done with the collector, so explicitly cancel it.
435
collector.cancel();
436
437         // Anonymous users can't have a roster.
438
roster = null;
439
440         // Set presence to online.
441
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
442
443         // Indicate that we're now authenticated.
444
authenticated = true;
445         anonymous = true;
446
447         // If debugging is enabled, change the the debug window title to include the
448
// name we are now logged-in as.
449
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
450
// will be null
451
if (DEBUG_ENABLED && debugger != null) {
452             debugger.userHasLogged(user);
453         }
454     }
455
456     /**
457      * Returns the roster for the user logged into the server. If the user has not yet
458      * logged into the server (or if the user is logged in anonymously), this method will return
459      * <tt>null</tt>.
460      *
461      * @return the user's roster, or <tt>null</tt> if the user has not logged in yet.
462      */

463     public Roster getRoster() {
464         if (roster == null) {
465             return null;
466         }
467         // If this is the first time the user has asked for the roster after calling
468
// login, we want to wait for the server to send back the user's roster. This
469
// behavior shields API users from having to worry about the fact that roster
470
// operations are asynchronous, although they'll still have to listen for
471
// changes to the roster. Note: because of this waiting logic, internal
472
// Smack code should be wary about calling the getRoster method, and may need to
473
// access the roster object directly.
474
if (!roster.rosterInitialized) {
475             try {
476                 synchronized (roster) {
477                     long waitTime = SmackConfiguration.getPacketReplyTimeout();
478                     long start = System.currentTimeMillis();
479                     while (!roster.rosterInitialized) {
480                         if (waitTime <= 0) {
481                             break;
482                         }
483                         roster.wait(waitTime);
484                         long now = System.currentTimeMillis();
485                         waitTime -= now - start;
486                         start = now;
487                     }
488                 }
489             }
490             catch (InterruptedException JavaDoc ie) { }
491         }
492         return roster;
493     }
494
495     /**
496      * Returns an account manager instance for this connection.
497      *
498      * @return an account manager for this connection.
499      */

500     public synchronized AccountManager getAccountManager() {
501         if (accountManager == null) {
502             accountManager = new AccountManager(this);
503         }
504         return accountManager;
505     }
506
507     /**
508      * Creates a new chat with the specified participant. The participant should
509      * be a valid XMPP user such as <tt>jdoe@jivesoftware.com</tt> or
510      * <tt>jdoe@jivesoftware.com/work</tt>.
511      *
512      * @param participant the person to start the conversation with.
513      * @return a new Chat object.
514      */

515     public Chat createChat(String JavaDoc participant) {
516         if (!isConnected()) {
517             throw new IllegalStateException JavaDoc("Not connected to server.");
518         }
519         return new Chat(this, participant);
520     }
521
522     /**
523      * Creates a new group chat connected to the specified room. The room name
524      * should be full address, such as <tt>room@chat.example.com</tt>.
525      * <p>
526      * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
527      * for the XMPP server example.com). You must ensure that the room address you're
528      * trying to connect to includes the proper chat sub-domain.
529      *
530      * @param room the fully qualifed name of the room.
531      * @return a new GroupChat object.
532      */

533     public GroupChat createGroupChat(String JavaDoc room) {
534         if (!isConnected()) {
535             throw new IllegalStateException JavaDoc("Not connected to server.");
536         }
537         return new GroupChat(this, room);
538     }
539
540     /**
541      * Returns true if currently connected to the XMPP server.
542      *
543      * @return true if connected.
544      */

545     public boolean isConnected() {
546         return connected;
547     }
548
549     /**
550      * Returns true if the connection is a secured one, such as an SSL connection.
551      *
552      * @return true if a secure connection to the server.
553      */

554     public boolean isSecureConnection() {
555         return false;
556     }
557
558     /**
559      * Returns true if currently authenticated by successfully calling the login method.
560      *
561      * @return true if authenticated.
562      */

563     public boolean isAuthenticated() {
564         return authenticated;
565     }
566
567     /**
568      * Returns true if currently authenticated anonymously.
569      *
570      * @return true if authenticated anonymously.
571      */

572     public boolean isAnonymous() {
573         return anonymous;
574     }
575
576     /**
577      * Closes the connection by setting presence to unavailable then closing the stream to
578      * the XMPP server. Once a connection has been closed, it cannot be re-opened.
579      */

580     public void close() {
581         // Set presence to offline.
582
packetWriter.sendPacket(new Presence(Presence.Type.UNAVAILABLE));
583         packetReader.shutdown();
584         packetWriter.shutdown();
585         // Wait 150 ms for processes to clean-up, then shutdown.
586
try {
587             Thread.sleep(150);
588         }
589         catch (Exception JavaDoc e) {
590         }
591
592         // Close down the readers and writers.
593
if (reader != null)
594         {
595             try { reader.close(); } catch (Throwable JavaDoc ignore) { }
596             reader = null;
597         }
598         if (writer != null)
599         {
600             try { writer.close(); } catch (Throwable JavaDoc ignore) { }
601             writer = null;
602         }
603
604         try {
605             socket.close();
606         }
607         catch (Exception JavaDoc e) {
608         }
609         authenticated = false;
610         connected = false;
611     }
612
613     /**
614      * Sends the specified packet to the server.
615      *
616      * @param packet the packet to send.
617      */

618     public void sendPacket(Packet packet) {
619         if (!isConnected()) {
620             throw new IllegalStateException JavaDoc("Not connected to server.");
621         }
622         if (packet == null) {
623             throw new NullPointerException JavaDoc("Packet is null.");
624         }
625         packetWriter.sendPacket(packet);
626     }
627
628     /**
629      * Registers a packet listener with this connection. A packet filter determines
630      * which packets will be delivered to the listener.
631      *
632      * @param packetListener the packet listener to notify of new packets.
633      * @param packetFilter the packet filter to use.
634      */

635     public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
636         if (!isConnected()) {
637             throw new IllegalStateException JavaDoc("Not connected to server.");
638         }
639         packetReader.addPacketListener(packetListener, packetFilter);
640     }
641
642     /**
643      * Removes a packet listener from this connection.
644      *
645      * @param packetListener the packet listener to remove.
646      */

647     public void removePacketListener(PacketListener packetListener) {
648         packetReader.removePacketListener(packetListener);
649     }
650
651     /**
652      * Registers a packet listener with this connection. The listener will be
653      * notified of every packet that this connection sends. A packet filter determines
654      * which packets will be delivered to the listener.
655      *
656      * @param packetListener the packet listener to notify of sent packets.
657      * @param packetFilter the packet filter to use.
658      */

659     public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) {
660         if (!isConnected()) {
661             throw new IllegalStateException JavaDoc("Not connected to server.");
662         }
663         packetWriter.addPacketListener(packetListener, packetFilter);
664     }
665
666     /**
667      * Removes a packet listener from this connection.
668      *
669      * @param packetListener the packet listener to remove.
670      */

671     public void removePacketWriterListener(PacketListener packetListener) {
672         packetWriter.removePacketListener(packetListener);
673     }
674
675     /**
676      * Creates a new packet collector for this connection. A packet filter determines
677      * which packets will be accumulated by the collector.
678      *
679      * @param packetFilter the packet filter to use.
680      * @return a new packet collector.
681      */

682     public PacketCollector createPacketCollector(PacketFilter packetFilter) {
683         return packetReader.createPacketCollector(packetFilter);
684     }
685
686     /**
687      * Adds a connection listener to this connection that will be notified when
688      * the connection closes or fails.
689      *
690      * @param connectionListener a connection listener.
691      */

692     public void addConnectionListener(ConnectionListener connectionListener) {
693         if (connectionListener == null) {
694             return;
695         }
696         synchronized (packetReader.connectionListeners) {
697             if (!packetReader.connectionListeners.contains(connectionListener)) {
698                 packetReader.connectionListeners.add(connectionListener);
699             }
700         }
701     }
702
703     /**
704      * Removes a connection listener from this connection.
705      *
706      * @param connectionListener a connection listener.
707      */

708     public void removeConnectionListener(ConnectionListener connectionListener) {
709         synchronized (packetReader.connectionListeners) {
710             packetReader.connectionListeners.remove(connectionListener);
711         }
712     }
713
714     /**
715      * Adds a connection established listener that will be notified when a new connection
716      * is established.
717      *
718      * @param connectionEstablishedListener a listener interested on connection established events.
719      */

720     public static void addConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
721         synchronized (connectionEstablishedListeners) {
722             if (!connectionEstablishedListeners.contains(connectionEstablishedListener)) {
723                 connectionEstablishedListeners.add(connectionEstablishedListener);
724             }
725         }
726     }
727
728     /**
729      * Removes a listener on new established connections.
730      *
731      * @param connectionEstablishedListener a listener interested on connection established events.
732      */

733     public static void removeConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
734         synchronized (connectionEstablishedListeners) {
735             connectionEstablishedListeners.remove(connectionEstablishedListener);
736         }
737     }
738
739     /**
740      * Initializes the connection by creating a packet reader and writer and opening a
741      * XMPP stream to the server.
742      *
743      * @throws XMPPException if establishing a connection to the server fails.
744      */

745     private void init() throws XMPPException {
746         // Set the reader and writer instance variables
747
initReaderAndWriter();
748
749         try
750         {
751             packetWriter = new PacketWriter(this);
752             packetReader = new PacketReader(this);
753
754             // If debugging is enabled, we should start the thread that will listen for
755
// all packets and then log them.
756
if (DEBUG_ENABLED) {
757                 packetReader.addPacketListener(debugger.getReaderListener(), null);
758                 if (debugger.getWriterListener() != null) {
759                     packetWriter.addPacketListener(debugger.getWriterListener(), null);
760                 }
761             }
762             // Start the packet writer. This will open a XMPP stream to the server
763
packetWriter.startup();
764             // Start the packet reader. The startup() method will block until we
765
// get an opening stream packet back from server.
766
packetReader.startup();
767
768             // Make note of the fact that we're now connected.
769
connected = true;
770
771             // Notify that a new connection has been established
772
connectionEstablished(this);
773         }
774         catch (XMPPException ex)
775         {
776             // An exception occurred in setting up the connection. Make sure we shut down the
777
// readers and writers and close the socket.
778

779             if (packetWriter != null) {
780                 try { packetWriter.shutdown(); } catch (Throwable JavaDoc ignore) { }
781                 packetWriter = null;
782             }
783             if (packetReader != null) {
784                 try { packetReader.shutdown(); } catch (Throwable JavaDoc ignore) { }
785                 packetReader = null;
786             }
787             if (reader != null) {
788                 try { reader.close(); } catch (Throwable JavaDoc ignore) { }
789                 reader = null;
790             }
791             if (writer != null) {
792                 try { writer.close(); } catch (Throwable JavaDoc ignore) { }
793                 writer = null;
794             }
795             if (socket != null) {
796                 try { socket.close(); } catch (Exception JavaDoc e) { }
797                 socket = null;
798             }
799             authenticated = false;
800             connected = false;
801
802             throw ex; // Everything stoppped. Now throw the exception.
803
}
804     }
805
806     private void initReaderAndWriter() throws XMPPException {
807         try {
808             reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
809             writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
810         }
811         catch (IOException ioe) {
812             throw new XMPPException(
813                 "XMPPError establishing connection with server.",
814                 new XMPPError(502),
815                 ioe);
816         }
817
818         // If debugging is enabled, we open a window and write out all network traffic.
819
if (DEBUG_ENABLED) {
820             if (debugger == null) {
821                 // Detect the debugger class to use.
822
String JavaDoc className = null;
823                 // Use try block since we may not have permission to get a system
824
// property (for example, when an applet).
825
try {
826                     className = System.getProperty("smack.debuggerClass");
827                 }
828                 catch (Throwable JavaDoc t) {
829                 }
830                 Class JavaDoc debuggerClass = null;
831                 if (className != null) {
832                     try {
833                         debuggerClass = Class.forName(className);
834                     }
835                     catch (Exception JavaDoc e) {
836                         e.printStackTrace();
837                     }
838                 }
839                 if (debuggerClass == null) {
840                     try {
841                         debuggerClass =
842                                 Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger");
843                     }
844                     catch (Exception JavaDoc ex) {
845                         try {
846                             debuggerClass = Class.forName("org.jivesoftware.smack.debugger.LiteDebugger");
847                         }
848                         catch (Exception JavaDoc ex2) {
849                             ex2.printStackTrace();
850                         }
851                     }
852                 }
853                 // Create a new debugger instance. If an exception occurs then disable the debugging
854
// option
855
try {
856                     Constructor JavaDoc constructor =
857                         debuggerClass.getConstructor(
858                             new Class JavaDoc[] { XMPPConnection.class, Writer.class, Reader.class });
859                     debugger = (SmackDebugger) constructor
860                             .newInstance(new Object JavaDoc[]{this, writer, reader});
861                     reader = debugger.getReader();
862                     writer = debugger.getWriter();
863                 }
864                 catch (Exception JavaDoc e) {
865                     e.printStackTrace();
866                     DEBUG_ENABLED = false;
867                 }
868             }
869             else {
870                 // Obtain new reader and writer from the existing debugger
871
reader = debugger.newConnectionReader(reader);
872                 writer = debugger.newConnectionWriter(writer);
873             }
874         }
875     }
876
877     /**
878      * Fires listeners on connection established events.
879      */

880     private static void connectionEstablished(XMPPConnection connection) {
881         ConnectionEstablishedListener[] listeners = null;
882         synchronized (connectionEstablishedListeners) {
883             listeners = new ConnectionEstablishedListener[connectionEstablishedListeners.size()];
884             connectionEstablishedListeners.toArray(listeners);
885         }
886         for (int i = 0; i < listeners.length; i++) {
887             listeners[i].connectionEstablished(connection);
888         }
889     }
890
891     /***********************************************
892      * TLS code below
893      **********************************************/

894
895     /**
896      * Returns true if the connection to the server has successfully negotiated TLS. Once TLS
897      * has been negotiatied the connection has been secured.
898      *
899      * @return true if the connection to the server has successfully negotiated TLS.
900      */

901     public boolean isUsingTLS() {
902         return usingTLS;
903     }
904
905     /**
906      * Returns the SASLAuthentication manager that is responsible for authenticating with
907      * the server.
908      *
909      * @return the SASLAuthentication manager that is responsible for authenticating with
910      * the server.
911      */

912     public SASLAuthentication getSASLAuthentication() {
913         return saslAuthentication;
914     }
915
916     /**
917      * Notification message saying that the server supports TLS so confirm the server that we
918      * want to secure the connection.
919      */

920     void startTLSReceived() {
921         try {
922             writer.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
923             writer.flush();
924         } catch (IOException e) {
925             packetReader.notifyConnectionError(e);
926         }
927     }
928
929     /**
930      * The server has indicated that TLS negotiation can start. We now need to secure the
931      * existing plain connection and perform a handshake. This method won't return until the
932      * connection has finished the handshake or an error occured while securing the connection.
933      */

934     void proceedTLSReceived() {
935         try {
936             SSLContext context = SSLContext.getInstance("TLS");
937             // Accept any certificate presented by the server
938
context.init(null, // KeyManager not required
939
new javax.net.ssl.TrustManager[] { new OpenTrustManager() },
940                     new java.security.SecureRandom JavaDoc());
941             Socket plain = socket;
942             // Secure the plain connection
943
socket = context.getSocketFactory().createSocket(plain,
944                     plain.getInetAddress().getHostName(), plain.getPort(), true);
945             socket.setSoTimeout(0);
946             socket.setKeepAlive(true);
947             // Initialize the reader and writer with the new secured version
948
initReaderAndWriter();
949             // Proceed to do the handshake
950
((SSLSocket)socket).startHandshake();
951
952             // Set that TLS was successful
953
usingTLS = true;
954
955             // Set the new writer to use
956
packetWriter.setWriter(writer);
957             // Send a new opening stream to the server
958
packetWriter.openStream();
959         }
960         catch (Exception JavaDoc e) {
961             packetReader.notifyConnectionError(e);
962         }
963     }
964
965 }
Popular Tags