001/** 002 * 003 * Copyright 2009 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack; 018 019import java.io.IOException; 020import java.io.Reader; 021import java.io.Writer; 022import java.lang.reflect.Constructor; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Map; 026import java.util.Set; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.ConcurrentLinkedQueue; 029import java.util.concurrent.CopyOnWriteArrayList; 030import java.util.concurrent.CopyOnWriteArraySet; 031import java.util.concurrent.ScheduledExecutorService; 032import java.util.concurrent.ScheduledFuture; 033import java.util.concurrent.ScheduledThreadPoolExecutor; 034import java.util.concurrent.ThreadFactory; 035import java.util.concurrent.TimeUnit; 036import java.util.concurrent.atomic.AtomicBoolean; 037import java.util.concurrent.atomic.AtomicInteger; 038import java.util.logging.Level; 039import java.util.logging.Logger; 040 041import javax.security.sasl.SaslException; 042 043import org.jivesoftware.smack.SmackException.NoResponseException; 044import org.jivesoftware.smack.SmackException.NotConnectedException; 045import org.jivesoftware.smack.SmackException.ConnectionException; 046import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException; 047import org.jivesoftware.smack.XMPPException.XMPPErrorException; 048import org.jivesoftware.smack.compression.XMPPInputOutputStream; 049import org.jivesoftware.smack.debugger.SmackDebugger; 050import org.jivesoftware.smack.filter.IQReplyFilter; 051import org.jivesoftware.smack.filter.PacketFilter; 052import org.jivesoftware.smack.packet.Bind; 053import org.jivesoftware.smack.packet.IQ; 054import org.jivesoftware.smack.packet.Packet; 055import org.jivesoftware.smack.packet.Presence; 056import org.jivesoftware.smack.packet.Session; 057 058/** 059 * The abstract XMPPConnection class provides an interface for connections to a XMPP server and 060 * implements shared methods which are used by the different types of connections (e.g. 061 * {@link XMPPTCPConnection} or {@link XMPPBOSHConnection}). To create a connection to a XMPP server 062 * a simple usage of this API might look like the following: 063 * <p> 064 * 065 * <pre> 066 * // Create a connection to the igniterealtime.org XMPP server. 067 * XMPPConnection con = new XMPPTCPConnection("igniterealtime.org"); 068 * // Connect to the server 069 * con.connect(); 070 * // Most servers require you to login before performing other tasks. 071 * con.login("jsmith", "mypass"); 072 * // Start a new conversation with John Doe and send him a message. 073 * Chat chat = ChatManager.getInstanceFor(con).createChat(<font color="green">"jdoe@igniterealtime.org"</font>, new MessageListener() { 074 * public void processMessage(Chat chat, Message message) { 075 * // Print out any messages we get back to standard out. 076 * System.out.println(<font color="green">"Received message: "</font> + message); 077 * } 078 * }); 079 * chat.sendMessage(<font color="green">"Howdy!"</font>); 080 * // Disconnect from the server 081 * con.disconnect(); 082 * </pre> 083 * <p> 084 * Connections can be reused between connections. This means that an Connection may be connected, 085 * disconnected and then connected again. Listeners of the Connection will be retained accross 086 * connections. 087 * <p> 088 * If a connected XMPPConnection gets disconnected abruptly and automatic reconnection is enabled ( 089 * {@link ConnectionConfiguration#isReconnectionAllowed()}, the default), then it will try to 090 * reconnect again. To stop the reconnection process, use {@link #disconnect()}. Once stopped you 091 * can use {@link #connect()} to manually connect to the server. 092 * 093 * @author Matt Tucker 094 * @author Guenther Niess 095 */ 096@SuppressWarnings("javadoc") 097public abstract class XMPPConnection { 098 private static final Logger LOGGER = Logger.getLogger(XMPPConnection.class.getName()); 099 100 /** 101 * Counter to uniquely identify connections that are created. 102 */ 103 private final static AtomicInteger connectionCounter = new AtomicInteger(0); 104 105 /** 106 * A set of listeners which will be invoked if a new connection is created. 107 */ 108 private final static Set<ConnectionCreationListener> connectionEstablishedListeners = 109 new CopyOnWriteArraySet<ConnectionCreationListener>(); 110 111 static { 112 // Ensure the SmackConfiguration class is loaded by calling a method in it. 113 SmackConfiguration.getVersion(); 114 } 115 116 /** 117 * A collection of ConnectionListeners which listen for connection closing 118 * and reconnection events. 119 */ 120 protected final Collection<ConnectionListener> connectionListeners = 121 new CopyOnWriteArrayList<ConnectionListener>(); 122 123 /** 124 * A collection of PacketCollectors which collects packets for a specified filter 125 * and perform blocking and polling operations on the result queue. 126 */ 127 protected final Collection<PacketCollector> collectors = new ConcurrentLinkedQueue<PacketCollector>(); 128 129 /** 130 * List of PacketListeners that will be notified when a new packet was received. 131 */ 132 protected final Map<PacketListener, ListenerWrapper> recvListeners = 133 new ConcurrentHashMap<PacketListener, ListenerWrapper>(); 134 135 /** 136 * List of PacketListeners that will be notified when a new packet was sent. 137 */ 138 protected final Map<PacketListener, ListenerWrapper> sendListeners = 139 new ConcurrentHashMap<PacketListener, ListenerWrapper>(); 140 141 /** 142 * List of PacketInterceptors that will be notified when a new packet is about to be 143 * sent to the server. These interceptors may modify the packet before it is being 144 * actually sent to the server. 145 */ 146 protected final Map<PacketInterceptor, InterceptorWrapper> interceptors = 147 new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>(); 148 149 /** 150 * 151 */ 152 private long packetReplyTimeout = SmackConfiguration.getDefaultPacketReplyTimeout(); 153 154 /** 155 * The SmackDebugger allows to log and debug XML traffic. 156 */ 157 protected SmackDebugger debugger = null; 158 159 /** 160 * The Reader which is used for the debugger. 161 */ 162 protected Reader reader; 163 164 /** 165 * The Writer which is used for the debugger. 166 */ 167 protected Writer writer; 168 169 170 /** 171 * The SASLAuthentication manager that is responsible for authenticating with the server. 172 */ 173 protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); 174 175 /** 176 * A number to uniquely identify connections that are created. This is distinct from the 177 * connection ID, which is a value sent by the server once a connection is made. 178 */ 179 protected final int connectionCounterValue = connectionCounter.getAndIncrement(); 180 181 /** 182 * Holds the initial configuration used while creating the connection. 183 */ 184 protected final ConnectionConfiguration config; 185 186 /** 187 * Holds the Caps Node information for the used XMPP service (i.e. the XMPP server) 188 */ 189 private String serviceCapsNode; 190 191 /** 192 * Defines how the from attribute of outgoing stanzas should be handled. 193 */ 194 private FromMode fromMode = FromMode.OMITTED; 195 196 /** 197 * Stores whether the server supports rosterVersioning 198 */ 199 private boolean rosterVersioningSupported = false; 200 201 protected XMPPInputOutputStream compressionHandler; 202 203 /** 204 * ExecutorService used to invoke the PacketListeners on newly arrived and parsed stanzas. It is 205 * important that we use a <b>single threaded ExecutorService</b> in order to guarantee that the 206 * PacketListeners are invoked in the same order the stanzas arrived. 207 */ 208 private final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, 209 new SmackExecutorThreadFactory(connectionCounterValue)); 210 211 /** 212 * SmackExecutorThreadFactory is a *static* inner class of XMPPConnection. Note that we must not 213 * use anonymous classes in order to prevent threads from leaking. 214 */ 215 private static final class SmackExecutorThreadFactory implements ThreadFactory { 216 private final int connectionCounterValue; 217 private int count = 0; 218 219 private SmackExecutorThreadFactory(int connectionCounterValue) { 220 this.connectionCounterValue = connectionCounterValue; 221 } 222 223 @Override 224 public Thread newThread(Runnable runnable) { 225 Thread thread = new Thread(runnable, "Smack Executor Service " + count++ + " (" 226 + connectionCounterValue + ")"); 227 thread.setDaemon(true); 228 return thread; 229 } 230 } 231 232 private Roster roster; 233 234 /** 235 * The used host to establish the connection to 236 */ 237 private String host; 238 239 /** 240 * The used port to establish the connection to 241 */ 242 private int port; 243 244 /** 245 * Set to true if the server requires the connection to be binded in order to continue. 246 * <p> 247 * Note that we use AtomicBoolean here because it allows us to set the Boolean *object*, which 248 * we also use as synchronization object. A plain non-atomic Boolean object would be newly created 249 * for every change of the boolean value, which makes it useless as object for wait()/notify(). 250 */ 251 private AtomicBoolean bindingRequired = new AtomicBoolean(false); 252 253 private boolean sessionSupported; 254 255 /** 256 * 257 */ 258 private IOException connectionException; 259 260 /** 261 * Flag that indicates if the user is currently authenticated with the server. 262 */ 263 protected boolean authenticated = false; 264 265 /** 266 * Flag that indicates if the user was authenticated with the server when the connection 267 * to the server was closed (abruptly or not). 268 */ 269 protected boolean wasAuthenticated = false; 270 271 /** 272 * Create a new XMPPConnection to a XMPP server. 273 * 274 * @param configuration The configuration which is used to establish the connection. 275 */ 276 protected XMPPConnection(ConnectionConfiguration configuration) { 277 config = configuration; 278 } 279 280 /** 281 * Returns the configuration used to connect to the server. 282 * 283 * @return the configuration used to connect to the server. 284 */ 285 protected ConnectionConfiguration getConfiguration() { 286 return config; 287 } 288 289 /** 290 * Returns the name of the service provided by the XMPP server for this connection. 291 * This is also called XMPP domain of the connected server. After 292 * authenticating with the server the returned value may be different. 293 * 294 * @return the name of the service provided by the XMPP server. 295 */ 296 public String getServiceName() { 297 return config.getServiceName(); 298 } 299 300 /** 301 * Returns the host name of the server where the XMPP server is running. This would be the 302 * IP address of the server or a name that may be resolved by a DNS server. 303 * 304 * @return the host name of the server where the XMPP server is running or null if not yet connected. 305 */ 306 public String getHost() { 307 return host; 308 } 309 310 /** 311 * Returns the port number of the XMPP server for this connection. The default port 312 * for normal connections is 5222. 313 * 314 * @return the port number of the XMPP server or 0 if not yet connected. 315 */ 316 public int getPort() { 317 return port; 318 } 319 320 /** 321 * Returns the full XMPP address of the user that is logged in to the connection or 322 * <tt>null</tt> if not logged in yet. An XMPP address is in the form 323 * username@server/resource. 324 * 325 * @return the full XMPP address of the user logged in. 326 */ 327 public abstract String getUser(); 328 329 /** 330 * Returns the connection ID for this connection, which is the value set by the server 331 * when opening a XMPP stream. If the server does not set a connection ID, this value 332 * will be null. This value will be <tt>null</tt> if not connected to the server. 333 * 334 * @return the ID of this connection returned from the XMPP server or <tt>null</tt> if 335 * not connected to the server. 336 */ 337 public abstract String getConnectionID(); 338 339 /** 340 * Returns true if currently connected to the XMPP server. 341 * 342 * @return true if connected. 343 */ 344 public abstract boolean isConnected(); 345 346 /** 347 * Returns true if currently authenticated by successfully calling the login method. 348 * 349 * @return true if authenticated. 350 */ 351 public abstract boolean isAuthenticated(); 352 353 /** 354 * Returns true if currently authenticated anonymously. 355 * 356 * @return true if authenticated anonymously. 357 */ 358 public abstract boolean isAnonymous(); 359 360 /** 361 * Returns true if the connection to the server has successfully negotiated encryption. 362 * 363 * @return true if a secure connection to the server. 364 */ 365 public abstract boolean isSecureConnection(); 366 367 protected abstract void sendPacketInternal(Packet packet) throws NotConnectedException; 368 369 /** 370 * Returns true if network traffic is being compressed. When using stream compression network 371 * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow 372 * speed network connection. However, the server will need to use more CPU time in order to 373 * un/compress network data so under high load the server performance might be affected. 374 * 375 * @return true if network traffic is being compressed. 376 */ 377 public abstract boolean isUsingCompression(); 378 379 /** 380 * Establishes a connection to the XMPP server and performs an automatic login 381 * only if the previous connection state was logged (authenticated). It basically 382 * creates and maintains a connection to the server. 383 * <p> 384 * Listeners will be preserved from a previous connection. 385 * 386 * @throws XMPPException if an error occurs on the XMPP protocol level. 387 * @throws SmackException if an error occurs somewhere else besides XMPP protocol level. 388 * @throws IOException 389 * @throws ConnectionException with detailed information about the failed connection. 390 */ 391 public void connect() throws SmackException, IOException, XMPPException { 392 saslAuthentication.init(); 393 bindingRequired.set(false); 394 sessionSupported = false; 395 connectionException = null; 396 connectInternal(); 397 } 398 399 /** 400 * Abstract method that concrete subclasses of XMPPConnection need to implement to perform their 401 * way of XMPP connection establishment. Implementations must guarantee that this method will 402 * block until the last features stanzas has been parsed and the features have been reported 403 * back to XMPPConnection (e.g. by calling @{link {@link XMPPConnection#serverRequiresBinding()} 404 * and such). 405 * <p> 406 * Also implementations are required to perform an automatic login if the previous connection 407 * state was logged (authenticated). 408 * 409 * @throws SmackException 410 * @throws IOException 411 * @throws XMPPException 412 */ 413 protected abstract void connectInternal() throws SmackException, IOException, XMPPException; 414 415 /** 416 * Logs in to the server using the strongest authentication mode supported by 417 * the server, then sets presence to available. If the server supports SASL authentication 418 * then the user will be authenticated using SASL if not Non-SASL authentication will 419 * be tried. If more than five seconds (default timeout) elapses in each step of the 420 * authentication process without a response from the server, or if an error occurs, a 421 * XMPPException will be thrown.<p> 422 * 423 * Before logging in (i.e. authenticate) to the server the connection must be connected. 424 * 425 * It is possible to log in without sending an initial available presence by using 426 * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is 427 * not interested in loading its roster upon login then use 428 * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. 429 * Finally, if you want to not pass a password and instead use a more advanced mechanism 430 * while using SASL then you may be interested in using 431 * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. 432 * For more advanced login settings see {@link ConnectionConfiguration}. 433 * 434 * @param username the username. 435 * @param password the password or <tt>null</tt> if using a CallbackHandler. 436 * @throws XMPPException if an error occurs on the XMPP protocol level. 437 * @throws SmackException if an error occurs somehwere else besides XMPP protocol level. 438 * @throws IOException 439 * @throws SaslException 440 */ 441 public void login(String username, String password) throws XMPPException, SmackException, SaslException, IOException { 442 login(username, password, "Smack"); 443 } 444 445 /** 446 * Logs in to the server using the strongest authentication mode supported by 447 * the server, then sets presence to available. If the server supports SASL authentication 448 * then the user will be authenticated using SASL if not Non-SASL authentication will 449 * be tried. If more than five seconds (default timeout) elapses in each step of the 450 * authentication process without a response from the server, or if an error occurs, a 451 * XMPPException will be thrown.<p> 452 * 453 * Before logging in (i.e. authenticate) to the server the connection must be connected. 454 * 455 * It is possible to log in without sending an initial available presence by using 456 * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is 457 * not interested in loading its roster upon login then use 458 * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. 459 * Finally, if you want to not pass a password and instead use a more advanced mechanism 460 * while using SASL then you may be interested in using 461 * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. 462 * For more advanced login settings see {@link ConnectionConfiguration}. 463 * 464 * @param username the username. 465 * @param password the password or <tt>null</tt> if using a CallbackHandler. 466 * @param resource the resource. 467 * @throws XMPPException if an error occurs on the XMPP protocol level. 468 * @throws SmackException if an error occurs somehwere else besides XMPP protocol level. 469 * @throws IOException 470 * @throws SaslException 471 */ 472 public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException; 473 474 /** 475 * Logs in to the server anonymously. Very few servers are configured to support anonymous 476 * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login 477 * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or 478 * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). 479 * 480 * @throws XMPPException if an error occurs on the XMPP protocol level. 481 * @throws SmackException if an error occurs somehwere else besides XMPP protocol level. 482 * @throws IOException 483 * @throws SaslException 484 */ 485 public abstract void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException; 486 487 /** 488 * Notification message saying that the server requires the client to bind a 489 * resource to the stream. 490 */ 491 protected void serverRequiresBinding() { 492 synchronized (bindingRequired) { 493 bindingRequired.set(true); 494 bindingRequired.notify(); 495 } 496 } 497 498 /** 499 * Notification message saying that the server supports sessions. When a server supports 500 * sessions the client needs to send a Session packet after successfully binding a resource 501 * for the session. 502 */ 503 protected void serverSupportsSession() { 504 sessionSupported = true; 505 } 506 507 protected String bindResourceAndEstablishSession(String resource) throws XMPPErrorException, 508 ResourceBindingNotOfferedException, NoResponseException, NotConnectedException { 509 510 synchronized (bindingRequired) { 511 if (!bindingRequired.get()) { 512 try { 513 bindingRequired.wait(getPacketReplyTimeout()); 514 } 515 catch (InterruptedException e) { 516 // Ignore 517 } 518 if (!bindingRequired.get()) { 519 // Server never offered resource binding, which is REQURIED in XMPP client and 520 // server 521 // implementations as per RFC6120 7.2 522 throw new ResourceBindingNotOfferedException(); 523 } 524 } 525 } 526 527 Bind bindResource = new Bind(); 528 bindResource.setResource(resource); 529 530 Bind response = (Bind) createPacketCollectorAndSend(bindResource).nextResultOrThrow(); 531 String userJID = response.getJid(); 532 533 if (sessionSupported && !getConfiguration().isLegacySessionDisabled()) { 534 Session session = new Session(); 535 createPacketCollectorAndSend(session).nextResultOrThrow(); 536 } 537 return userJID; 538 } 539 540 protected void setConnectionException(IOException exception) { 541 connectionException = exception; 542 } 543 544 protected void throwConnectionExceptionOrNoResponse() throws IOException, NoResponseException { 545 if (connectionException != null) { 546 throw connectionException; 547 } else { 548 throw new NoResponseException(); 549 } 550 } 551 552 protected Reader getReader() { 553 return reader; 554 } 555 556 protected Writer getWriter() { 557 return writer; 558 } 559 560 protected void setServiceName(String serviceName) { 561 config.setServiceName(serviceName); 562 } 563 564 protected void setLoginInfo(String username, String password, String resource) { 565 config.setLoginInfo(username, password, resource); 566 } 567 568 protected void serverSupportsAccountCreation() { 569 AccountManager.getInstance(this).setSupportsAccountCreation(true); 570 } 571 572 protected void maybeResolveDns() throws Exception { 573 config.maybeResolveDns(); 574 } 575 576 /** 577 * Sends the specified packet to the server. 578 * 579 * @param packet the packet to send. 580 * @throws NotConnectedException 581 */ 582 public void sendPacket(Packet packet) throws NotConnectedException { 583 if (!isConnected()) { 584 throw new NotConnectedException(); 585 } 586 if (packet == null) { 587 throw new NullPointerException("Packet is null."); 588 } 589 switch (fromMode) { 590 case OMITTED: 591 packet.setFrom(null); 592 break; 593 case USER: 594 packet.setFrom(getUser()); 595 break; 596 case UNCHANGED: 597 default: 598 break; 599 } 600 // Invoke interceptors for the new packet that is about to be sent. Interceptors may modify 601 // the content of the packet. 602 firePacketInterceptors(packet); 603 sendPacketInternal(packet); 604 // Process packet writer listeners. Note that we're using the sending thread so it's 605 // expected that listeners are fast. 606 firePacketSendingListeners(packet); 607 } 608 609 /** 610 * Returns the roster for the user. 611 * <p> 612 * This method will never return <code>null</code>, instead if the user has not yet logged into 613 * the server or is logged in anonymously all modifying methods of the returned roster object 614 * like {@link Roster#createEntry(String, String, String[])}, 615 * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing 616 * {@link RosterListener}s will throw an IllegalStateException. 617 * 618 * @return the user's roster. 619 * @throws IllegalStateException if the connection is anonymous 620 */ 621 public Roster getRoster() { 622 if (isAnonymous()) { 623 throw new IllegalStateException("Anonymous users can't have a roster"); 624 } 625 // synchronize against login() 626 synchronized(this) { 627 if (roster == null) { 628 roster = new Roster(this); 629 } 630 if (!isAuthenticated()) { 631 return roster; 632 } 633 } 634 635 // If this is the first time the user has asked for the roster after calling 636 // login, we want to wait for the server to send back the user's roster. This 637 // behavior shields API users from having to worry about the fact that roster 638 // operations are asynchronous, although they'll still have to listen for 639 // changes to the roster. Note: because of this waiting logic, internal 640 // Smack code should be wary about calling the getRoster method, and may need to 641 // access the roster object directly. 642 // Also only check for rosterInitalized is isRosterLoadedAtLogin is set, otherwise the user 643 // has to manually call Roster.reload() before he can expect a initialized roster. 644 if (!roster.rosterInitialized && config.isRosterLoadedAtLogin()) { 645 try { 646 synchronized (roster) { 647 long waitTime = getPacketReplyTimeout(); 648 long start = System.currentTimeMillis(); 649 while (!roster.rosterInitialized) { 650 if (waitTime <= 0) { 651 break; 652 } 653 roster.wait(waitTime); 654 long now = System.currentTimeMillis(); 655 waitTime -= now - start; 656 start = now; 657 } 658 } 659 } 660 catch (InterruptedException ie) { 661 // Ignore. 662 } 663 } 664 return roster; 665 } 666 667 /** 668 * Returns the SASLAuthentication manager that is responsible for authenticating with 669 * the server. 670 * 671 * @return the SASLAuthentication manager that is responsible for authenticating with 672 * the server. 673 */ 674 protected SASLAuthentication getSASLAuthentication() { 675 return saslAuthentication; 676 } 677 678 /** 679 * Closes the connection by setting presence to unavailable then closing the connection to 680 * the XMPP server. The XMPPConnection can still be used for connecting to the server 681 * again. 682 * 683 * @throws NotConnectedException 684 */ 685 public void disconnect() throws NotConnectedException { 686 disconnect(new Presence(Presence.Type.unavailable)); 687 } 688 689 /** 690 * Closes the connection. A custom unavailable presence is sent to the server, followed 691 * by closing the stream. The XMPPConnection can still be used for connecting to the server 692 * again. A custom unavailable presence is useful for communicating offline presence 693 * information such as "On vacation". Typically, just the status text of the presence 694 * packet is set with online information, but most XMPP servers will deliver the full 695 * presence packet with whatever data is set. 696 * 697 * @param unavailablePresence the presence packet to send during shutdown. 698 * @throws NotConnectedException 699 */ 700 public synchronized void disconnect(Presence unavailablePresence) throws NotConnectedException { 701 if (!isConnected()) { 702 return; 703 } 704 705 sendPacket(unavailablePresence); 706 shutdown(); 707 callConnectionClosedListener(); 708 }; 709 710 /** 711 * Shuts the current connection down. 712 */ 713 protected abstract void shutdown(); 714 715 /** 716 * Adds a new listener that will be notified when new Connections are created. Note 717 * that newly created connections will not be actually connected to the server. 718 * 719 * @param connectionCreationListener a listener interested on new connections. 720 */ 721 public static void addConnectionCreationListener( 722 ConnectionCreationListener connectionCreationListener) { 723 connectionEstablishedListeners.add(connectionCreationListener); 724 } 725 726 /** 727 * Removes a listener that was interested in connection creation events. 728 * 729 * @param connectionCreationListener a listener interested on new connections. 730 */ 731 public static void removeConnectionCreationListener( 732 ConnectionCreationListener connectionCreationListener) { 733 connectionEstablishedListeners.remove(connectionCreationListener); 734 } 735 736 /** 737 * Get the collection of listeners that are interested in connection creation events. 738 * 739 * @return a collection of listeners interested on new connections. 740 */ 741 protected static Collection<ConnectionCreationListener> getConnectionCreationListeners() { 742 return Collections.unmodifiableCollection(connectionEstablishedListeners); 743 } 744 745 /** 746 * Adds a connection listener to this connection that will be notified when 747 * the connection closes or fails. 748 * 749 * @param connectionListener a connection listener. 750 */ 751 public void addConnectionListener(ConnectionListener connectionListener) { 752 if (connectionListener == null) { 753 return; 754 } 755 if (!connectionListeners.contains(connectionListener)) { 756 connectionListeners.add(connectionListener); 757 } 758 } 759 760 /** 761 * Removes a connection listener from this connection. 762 * 763 * @param connectionListener a connection listener. 764 */ 765 public void removeConnectionListener(ConnectionListener connectionListener) { 766 connectionListeners.remove(connectionListener); 767 } 768 769 /** 770 * Get the collection of listeners that are interested in connection events. 771 * 772 * @return a collection of listeners interested on connection events. 773 */ 774 protected Collection<ConnectionListener> getConnectionListeners() { 775 return connectionListeners; 776 } 777 778 /** 779 * Creates a new packet collector collecting packets that are replies to <code>packet</code>. 780 * Does also send <code>packet</code>. The packet filter for the collector is an 781 * {@link IQReplyFilter}, guaranteeing that packet id and JID in the 'from' address have 782 * expected values. 783 * 784 * @param packet the packet to filter responses from 785 * @return a new packet collector. 786 * @throws NotConnectedException 787 */ 788 public PacketCollector createPacketCollectorAndSend(IQ packet) throws NotConnectedException { 789 PacketFilter packetFilter = new IQReplyFilter(packet, this); 790 // Create the packet collector before sending the packet 791 PacketCollector packetCollector = createPacketCollector(packetFilter); 792 // Now we can send the packet as the collector has been created 793 sendPacket(packet); 794 return packetCollector; 795 } 796 797 /** 798 * Creates a new packet collector for this connection. A packet filter determines 799 * which packets will be accumulated by the collector. A PacketCollector is 800 * more suitable to use than a {@link PacketListener} when you need to wait for 801 * a specific result. 802 * 803 * @param packetFilter the packet filter to use. 804 * @return a new packet collector. 805 */ 806 public PacketCollector createPacketCollector(PacketFilter packetFilter) { 807 PacketCollector collector = new PacketCollector(this, packetFilter); 808 // Add the collector to the list of active collectors. 809 collectors.add(collector); 810 return collector; 811 } 812 813 /** 814 * Remove a packet collector of this connection. 815 * 816 * @param collector a packet collectors which was created for this connection. 817 */ 818 protected void removePacketCollector(PacketCollector collector) { 819 collectors.remove(collector); 820 } 821 822 /** 823 * Get the collection of all packet collectors for this connection. 824 * 825 * @return a collection of packet collectors for this connection. 826 */ 827 protected Collection<PacketCollector> getPacketCollectors() { 828 return collectors; 829 } 830 831 /** 832 * Registers a packet listener with this connection. A packet listener will be invoked only 833 * when an incoming packet is received. A packet filter determines 834 * which packets will be delivered to the listener. If the same packet listener 835 * is added again with a different filter, only the new filter will be used. 836 * 837 * <p> 838 * NOTE: If you want get a similar callback for outgoing packets, see {@link #addPacketInterceptor(PacketInterceptor, PacketFilter)}. 839 * 840 * @param packetListener the packet listener to notify of new received packets. 841 * @param packetFilter the packet filter to use. 842 */ 843 public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { 844 if (packetListener == null) { 845 throw new NullPointerException("Packet listener is null."); 846 } 847 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 848 recvListeners.put(packetListener, wrapper); 849 } 850 851 /** 852 * Removes a packet listener for received packets from this connection. 853 * 854 * @param packetListener the packet listener to remove. 855 */ 856 public void removePacketListener(PacketListener packetListener) { 857 recvListeners.remove(packetListener); 858 } 859 860 /** 861 * Get a map of all packet listeners for received packets of this connection. 862 * 863 * @return a map of all packet listeners for received packets. 864 */ 865 protected Map<PacketListener, ListenerWrapper> getPacketListeners() { 866 return recvListeners; 867 } 868 869 /** 870 * Registers a packet listener with this connection. The listener will be 871 * notified of every packet that this connection sends. A packet filter determines 872 * which packets will be delivered to the listener. Note that the thread 873 * that writes packets will be used to invoke the listeners. Therefore, each 874 * packet listener should complete all operations quickly or use a different 875 * thread for processing. 876 * 877 * @param packetListener the packet listener to notify of sent packets. 878 * @param packetFilter the packet filter to use. 879 */ 880 public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { 881 if (packetListener == null) { 882 throw new NullPointerException("Packet listener is null."); 883 } 884 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 885 sendListeners.put(packetListener, wrapper); 886 } 887 888 /** 889 * Removes a packet listener for sending packets from this connection. 890 * 891 * @param packetListener the packet listener to remove. 892 */ 893 public void removePacketSendingListener(PacketListener packetListener) { 894 sendListeners.remove(packetListener); 895 } 896 897 /** 898 * Get a map of all packet listeners for sending packets of this connection. 899 * 900 * @return a map of all packet listeners for sent packets. 901 */ 902 protected Map<PacketListener, ListenerWrapper> getPacketSendingListeners() { 903 return sendListeners; 904 } 905 906 907 /** 908 * Process all packet listeners for sending packets. 909 * 910 * @param packet the packet to process. 911 */ 912 private void firePacketSendingListeners(Packet packet) { 913 // Notify the listeners of the new sent packet 914 for (ListenerWrapper listenerWrapper : sendListeners.values()) { 915 try { 916 listenerWrapper.notifyListener(packet); 917 } 918 catch (NotConnectedException e) { 919 LOGGER.log(Level.WARNING, "Got not connected exception, aborting"); 920 break; 921 } 922 } 923 } 924 925 /** 926 * Registers a packet interceptor with this connection. The interceptor will be 927 * invoked every time a packet is about to be sent by this connection. Interceptors 928 * may modify the packet to be sent. A packet filter determines which packets 929 * will be delivered to the interceptor. 930 * 931 * <p> 932 * NOTE: For a similar functionality on incoming packets, see {@link #addPacketListener(PacketListener, PacketFilter)}. 933 * 934 * @param packetInterceptor the packet interceptor to notify of packets about to be sent. 935 * @param packetFilter the packet filter to use. 936 */ 937 public void addPacketInterceptor(PacketInterceptor packetInterceptor, 938 PacketFilter packetFilter) { 939 if (packetInterceptor == null) { 940 throw new NullPointerException("Packet interceptor is null."); 941 } 942 interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); 943 } 944 945 /** 946 * Removes a packet interceptor. 947 * 948 * @param packetInterceptor the packet interceptor to remove. 949 */ 950 public void removePacketInterceptor(PacketInterceptor packetInterceptor) { 951 interceptors.remove(packetInterceptor); 952 } 953 954 /** 955 * Get a map of all packet interceptors for sending packets of this connection. 956 * 957 * @return a map of all packet interceptors for sending packets. 958 */ 959 protected Map<PacketInterceptor, InterceptorWrapper> getPacketInterceptors() { 960 return interceptors; 961 } 962 963 /** 964 * Process interceptors. Interceptors may modify the packet that is about to be sent. 965 * Since the thread that requested to send the packet will invoke all interceptors, it 966 * is important that interceptors perform their work as soon as possible so that the 967 * thread does not remain blocked for a long period. 968 * 969 * @param packet the packet that is going to be sent to the server 970 */ 971 private void firePacketInterceptors(Packet packet) { 972 if (packet != null) { 973 for (InterceptorWrapper interceptorWrapper : interceptors.values()) { 974 interceptorWrapper.notifyListener(packet); 975 } 976 } 977 } 978 979 /** 980 * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} 981 * by setup the system property <code>smack.debuggerClass</code> to the implementation. 982 * 983 * @throws IllegalStateException if the reader or writer isn't yet initialized. 984 * @throws IllegalArgumentException if the SmackDebugger can't be loaded. 985 */ 986 protected void initDebugger() { 987 if (reader == null || writer == null) { 988 throw new NullPointerException("Reader or writer isn't initialized."); 989 } 990 // If debugging is enabled, we open a window and write out all network traffic. 991 if (config.isDebuggerEnabled()) { 992 if (debugger == null) { 993 // Detect the debugger class to use. 994 String className = null; 995 // Use try block since we may not have permission to get a system 996 // property (for example, when an applet). 997 try { 998 className = System.getProperty("smack.debuggerClass"); 999 } 1000 catch (Throwable t) { 1001 // Ignore. 1002 } 1003 Class<?> debuggerClass = null; 1004 if (className != null) { 1005 try { 1006 debuggerClass = Class.forName(className); 1007 } 1008 catch (Exception e) { 1009 LOGGER.warning("Unabled to instantiate debugger class " + className); 1010 } 1011 } 1012 if (debuggerClass == null) { 1013 try { 1014 debuggerClass = 1015 Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); 1016 } 1017 catch (Exception ex) { 1018 try { 1019 debuggerClass = 1020 Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); 1021 } 1022 catch (Exception ex2) { 1023 LOGGER.warning("Unabled to instantiate either Smack debugger class"); 1024 } 1025 } 1026 } 1027 // Create a new debugger instance. If an exception occurs then disable the debugging 1028 // option 1029 try { 1030 Constructor<?> constructor = debuggerClass 1031 .getConstructor(XMPPConnection.class, Writer.class, Reader.class); 1032 debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); 1033 reader = debugger.getReader(); 1034 writer = debugger.getWriter(); 1035 } 1036 catch (Exception e) { 1037 throw new IllegalArgumentException("Can't initialize the configured debugger!", e); 1038 } 1039 } 1040 else { 1041 // Obtain new reader and writer from the existing debugger 1042 reader = debugger.newConnectionReader(reader); 1043 writer = debugger.newConnectionWriter(writer); 1044 } 1045 } 1046 } 1047 1048 /** 1049 * Set the servers Entity Caps node 1050 * 1051 * XMPPConnection holds this information in order to avoid a dependency to 1052 * smackx where EntityCapsManager lives from smack. 1053 * 1054 * @param node 1055 */ 1056 protected void setServiceCapsNode(String node) { 1057 serviceCapsNode = node; 1058 } 1059 1060 /** 1061 * Retrieve the servers Entity Caps node 1062 * 1063 * XMPPConnection holds this information in order to avoid a dependency to 1064 * smackx where EntityCapsManager lives from smack. 1065 * 1066 * @return the servers entity caps node 1067 */ 1068 public String getServiceCapsNode() { 1069 return serviceCapsNode; 1070 } 1071 1072 /** 1073 * Returns true if the server supports roster versioning as defined in XEP-0237. 1074 * 1075 * @return true if the server supports roster versioning 1076 */ 1077 public boolean isRosterVersioningSupported() { 1078 return rosterVersioningSupported; 1079 } 1080 1081 /** 1082 * Indicates that the server supports roster versioning as defined in XEP-0237. 1083 */ 1084 protected void setRosterVersioningSupported() { 1085 rosterVersioningSupported = true; 1086 } 1087 1088 /** 1089 * Returns the current value of the reply timeout in milliseconds for request for this 1090 * XMPPConnection instance. 1091 * 1092 * @return the packet reply timeout in milliseconds 1093 */ 1094 public long getPacketReplyTimeout() { 1095 return packetReplyTimeout; 1096 } 1097 1098 /** 1099 * Set the packet reply timeout in milliseconds. In most cases, Smack will throw a 1100 * {@link NoResponseException} if no reply to a request was received within the timeout period. 1101 * 1102 * @param timeout the packet reply timeout in milliseconds 1103 */ 1104 public void setPacketReplyTimeout(long timeout) { 1105 packetReplyTimeout = timeout; 1106 } 1107 1108 /** 1109 * Processes a packet after it's been fully parsed by looping through the installed 1110 * packet collectors and listeners and letting them examine the packet to see if 1111 * they are a match with the filter. 1112 * 1113 * @param packet the packet to process. 1114 */ 1115 protected void processPacket(Packet packet) { 1116 if (packet == null) { 1117 return; 1118 } 1119 1120 // Loop through all collectors and notify the appropriate ones. 1121 for (PacketCollector collector: getPacketCollectors()) { 1122 collector.processPacket(packet); 1123 } 1124 1125 // Deliver the incoming packet to listeners. 1126 executorService.submit(new ListenerNotification(packet)); 1127 } 1128 1129 /** 1130 * A runnable to notify all listeners of a packet. 1131 */ 1132 private class ListenerNotification implements Runnable { 1133 1134 private Packet packet; 1135 1136 public ListenerNotification(Packet packet) { 1137 this.packet = packet; 1138 } 1139 1140 public void run() { 1141 for (ListenerWrapper listenerWrapper : recvListeners.values()) { 1142 try { 1143 listenerWrapper.notifyListener(packet); 1144 } catch(NotConnectedException e) { 1145 LOGGER.log(Level.WARNING, "Got not connected exception, aborting", e); 1146 break; 1147 } catch (Exception e) { 1148 LOGGER.log(Level.SEVERE, "Exception in packet listener", e); 1149 } 1150 } 1151 } 1152 } 1153 1154 /** 1155 * Sets whether the connection has already logged in the server. This method assures that the 1156 * {@link #wasAuthenticated} flag is never reset once it has ever been set. 1157 * 1158 * @param authenticated true if the connection has already been authenticated. 1159 */ 1160 protected void setWasAuthenticated(boolean authenticated) { 1161 // Never reset the flag if the connection has ever been authenticated 1162 if (!wasAuthenticated) { 1163 wasAuthenticated = authenticated; 1164 } 1165 } 1166 1167 protected void callConnectionConnectedListener() { 1168 for (ConnectionListener listener : getConnectionListeners()) { 1169 listener.connected(this); 1170 } 1171 } 1172 1173 protected void callConnectionAuthenticatedListener() { 1174 for (ConnectionListener listener : getConnectionListeners()) { 1175 listener.authenticated(this); 1176 } 1177 } 1178 1179 void callConnectionClosedListener() { 1180 for (ConnectionListener listener : getConnectionListeners()) { 1181 try { 1182 listener.connectionClosed(); 1183 } 1184 catch (Exception e) { 1185 // Catch and print any exception so we can recover 1186 // from a faulty listener and finish the shutdown process 1187 LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e); 1188 } 1189 } 1190 } 1191 1192 protected void callConnectionClosedOnErrorListener(Exception e) { 1193 LOGGER.log(Level.WARNING, "Connection closed with error", e); 1194 for (ConnectionListener listener : getConnectionListeners()) { 1195 try { 1196 listener.connectionClosedOnError(e); 1197 } 1198 catch (Exception e2) { 1199 // Catch and print any exception so we can recover 1200 // from a faulty listener 1201 LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e2); 1202 } 1203 } 1204 } 1205 1206 /** 1207 * A wrapper class to associate a packet filter with a listener. 1208 */ 1209 protected static class ListenerWrapper { 1210 1211 private PacketListener packetListener; 1212 private PacketFilter packetFilter; 1213 1214 /** 1215 * Create a class which associates a packet filter with a listener. 1216 * 1217 * @param packetListener the packet listener. 1218 * @param packetFilter the associated filter or null if it listen for all packets. 1219 */ 1220 public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { 1221 this.packetListener = packetListener; 1222 this.packetFilter = packetFilter; 1223 } 1224 1225 /** 1226 * Notify and process the packet listener if the filter matches the packet. 1227 * 1228 * @param packet the packet which was sent or received. 1229 * @throws NotConnectedException 1230 */ 1231 public void notifyListener(Packet packet) throws NotConnectedException { 1232 if (packetFilter == null || packetFilter.accept(packet)) { 1233 packetListener.processPacket(packet); 1234 } 1235 } 1236 } 1237 1238 /** 1239 * A wrapper class to associate a packet filter with an interceptor. 1240 */ 1241 protected static class InterceptorWrapper { 1242 1243 private PacketInterceptor packetInterceptor; 1244 private PacketFilter packetFilter; 1245 1246 /** 1247 * Create a class which associates a packet filter with an interceptor. 1248 * 1249 * @param packetInterceptor the interceptor. 1250 * @param packetFilter the associated filter or null if it intercepts all packets. 1251 */ 1252 public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { 1253 this.packetInterceptor = packetInterceptor; 1254 this.packetFilter = packetFilter; 1255 } 1256 1257 public boolean equals(Object object) { 1258 if (object == null) { 1259 return false; 1260 } 1261 if (object instanceof InterceptorWrapper) { 1262 return ((InterceptorWrapper) object).packetInterceptor 1263 .equals(this.packetInterceptor); 1264 } 1265 else if (object instanceof PacketInterceptor) { 1266 return object.equals(this.packetInterceptor); 1267 } 1268 return false; 1269 } 1270 1271 /** 1272 * Notify and process the packet interceptor if the filter matches the packet. 1273 * 1274 * @param packet the packet which will be sent. 1275 */ 1276 public void notifyListener(Packet packet) { 1277 if (packetFilter == null || packetFilter.accept(packet)) { 1278 packetInterceptor.interceptPacket(packet); 1279 } 1280 } 1281 } 1282 1283 protected ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { 1284 return executorService.schedule(command, delay, unit); 1285 } 1286 1287 /** 1288 * Get the connection counter of this XMPPConnection instance. Those can be used as ID to 1289 * identify the connection, but beware that the ID may not be unique if you create more then 1290 * <tt>2*Integer.MAX_VALUE</tt> instances as the counter could wrap. 1291 * 1292 * @return the connection counter of this XMPPConnection 1293 */ 1294 public int getConnectionCounter() { 1295 return connectionCounterValue; 1296 } 1297 1298 public static enum FromMode { 1299 /** 1300 * Leave the 'from' attribute unchanged. This is the behavior of Smack < 4.0 1301 */ 1302 UNCHANGED, 1303 /** 1304 * Omit the 'from' attribute. According to RFC 6120 8.1.2.1 1. XMPP servers "MUST (...) 1305 * override the 'from' attribute specified by the client". It is therefore safe to specify 1306 * FromMode.OMITTED here. 1307 */ 1308 OMITTED, 1309 /** 1310 * Set the from to the clients full JID. This is usually not required. 1311 */ 1312 USER 1313 } 1314 1315 /** 1316 * Set the FromMode for this connection instance. Defines how the 'from' attribute of outgoing 1317 * stanzas should be populated by Smack. 1318 * 1319 * @param fromMode 1320 */ 1321 public void setFromMode(FromMode fromMode) { 1322 this.fromMode = fromMode; 1323 } 1324 1325 /** 1326 * Get the currently active FromMode. 1327 * 1328 * @return the currently active {@link FromMode} 1329 */ 1330 public FromMode getFromMode() { 1331 return this.fromMode; 1332 } 1333 1334 @Override 1335 protected void finalize() throws Throwable { 1336 try { 1337 // It's usually not a good idea to rely on finalize. But this is the easiest way to 1338 // avoid the "Smack Listener Processor" leaking. The thread(s) of the executor have a 1339 // reference to their ExecutorService which prevents the ExecutorService from being 1340 // gc'ed. It is possible that the XMPPConnection instance is gc'ed while the 1341 // listenerExecutor ExecutorService call not be gc'ed until it got shut down. 1342 executorService.shutdownNow(); 1343 } 1344 finally { 1345 super.finalize(); 1346 } 1347 } 1348}