1/** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2009 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 21package org.jivesoftware.smack; 22 23import java.io.Reader; 24import java.io.Writer; 25import java.lang.reflect.Constructor; 26import java.util.ArrayList; 27import java.util.Collection; 28import java.util.Collections; 29import java.util.List; 30import java.util.Map; 31import java.util.Set; 32import java.util.concurrent.ConcurrentHashMap; 33import java.util.concurrent.ConcurrentLinkedQueue; 34import java.util.concurrent.CopyOnWriteArrayList; 35import java.util.concurrent.CopyOnWriteArraySet; 36import java.util.concurrent.atomic.AtomicInteger; 37 38import org.jivesoftware.smack.compression.JzlibInputOutputStream; 39import org.jivesoftware.smack.compression.XMPPInputOutputStream; 40import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream; 41import org.jivesoftware.smack.debugger.SmackDebugger; 42import org.jivesoftware.smack.filter.PacketFilter; 43import org.jivesoftware.smack.packet.Packet; 44import org.jivesoftware.smack.packet.Presence; 45 46/** 47 * The abstract Connection class provides an interface for connections to a 48 * XMPP server and implements shared methods which are used by the 49 * different types of connections (e.g. XMPPConnection or BoshConnection). 50 * 51 * To create a connection to a XMPP server a simple usage of this API might 52 * look like the following: 53 * <pre> 54 * // Create a connection to the igniterealtime.org XMPP server. 55 * Connection con = new XMPPConnection("igniterealtime.org"); 56 * // Connect to the server 57 * con.connect(); 58 * // Most servers require you to login before performing other tasks. 59 * con.login("jsmith", "mypass"); 60 * // Start a new conversation with John Doe and send him a message. 61 * Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org"</font>, new MessageListener() { 62 * <p/> 63 * public void processMessage(Chat chat, Message message) { 64 * // Print out any messages we get back to standard out. 65 * System.out.println(<font color="green">"Received message: "</font> + message); 66 * } 67 * }); 68 * chat.sendMessage(<font color="green">"Howdy!"</font>); 69 * // Disconnect from the server 70 * con.disconnect(); 71 * </pre> 72 * <p/> 73 * Connections can be reused between connections. This means that an Connection 74 * may be connected, disconnected and then connected again. Listeners of the Connection 75 * will be retained accross connections.<p> 76 * <p/> 77 * If a connected Connection gets disconnected abruptly then it will try to reconnect 78 * again. To stop the reconnection process, use {@link #disconnect()}. Once stopped 79 * you can use {@link #connect()} to manually connect to the server. 80 * 81 * @see XMPPConnection 82 * @author Matt Tucker 83 * @author Guenther Niess 84 */ 85public abstract class Connection { 86 87 /** 88 * Counter to uniquely identify connections that are created. 89 */ 90 private final static AtomicInteger connectionCounter = new AtomicInteger(0); 91 92 /** 93 * A set of listeners which will be invoked if a new connection is created. 94 */ 95 private final static Set<ConnectionCreationListener> connectionEstablishedListeners = 96 new CopyOnWriteArraySet<ConnectionCreationListener>(); 97 98 protected final static List<XMPPInputOutputStream> compressionHandlers = new ArrayList<XMPPInputOutputStream>(2); 99 100 /** 101 * Value that indicates whether debugging is enabled. When enabled, a debug 102 * window will apear for each new connection that will contain the following 103 * information:<ul> 104 * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server. 105 * <li> Server Traffic -- raw XML traffic sent by the server to the client. 106 * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack. 107 * </ul> 108 * <p/> 109 * Debugging can be enabled by setting this field to true, or by setting the Java system 110 * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the 111 * command line such as "java SomeApp -Dsmack.debugEnabled=true". 112 */ 113 public static boolean DEBUG_ENABLED = false; 114 115 static { 116 // Use try block since we may not have permission to get a system 117 // property (for example, when an applet). 118 try { 119 DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); 120 } 121 catch (Exception e) { 122 // Ignore. 123 } 124 // Ensure the SmackConfiguration class is loaded by calling a method in it. 125 SmackConfiguration.getVersion(); 126 // Add the Java7 compression handler first, since it's preferred 127 compressionHandlers.add(new Java7ZlibInputOutputStream()); 128 // If we don't have access to the Java7 API use the JZlib compression handler 129 compressionHandlers.add(new JzlibInputOutputStream()); 130 } 131 132 /** 133 * A collection of ConnectionListeners which listen for connection closing 134 * and reconnection events. 135 */ 136 protected final Collection<ConnectionListener> connectionListeners = 137 new CopyOnWriteArrayList<ConnectionListener>(); 138 139 /** 140 * A collection of PacketCollectors which collects packets for a specified filter 141 * and perform blocking and polling operations on the result queue. 142 */ 143 protected final Collection<PacketCollector> collectors = new ConcurrentLinkedQueue<PacketCollector>(); 144 145 /** 146 * List of PacketListeners that will be notified when a new packet was received. 147 */ 148 protected final Map<PacketListener, ListenerWrapper> recvListeners = 149 new ConcurrentHashMap<PacketListener, ListenerWrapper>(); 150 151 /** 152 * List of PacketListeners that will be notified when a new packet was sent. 153 */ 154 protected final Map<PacketListener, ListenerWrapper> sendListeners = 155 new ConcurrentHashMap<PacketListener, ListenerWrapper>(); 156 157 /** 158 * List of PacketInterceptors that will be notified when a new packet is about to be 159 * sent to the server. These interceptors may modify the packet before it is being 160 * actually sent to the server. 161 */ 162 protected final Map<PacketInterceptor, InterceptorWrapper> interceptors = 163 new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>(); 164 165 /** 166 * The AccountManager allows creation and management of accounts on an XMPP server. 167 */ 168 private AccountManager accountManager = null; 169 170 /** 171 * The ChatManager keeps track of references to all current chats. 172 */ 173 protected ChatManager chatManager = null; 174 175 /** 176 * The SmackDebugger allows to log and debug XML traffic. 177 */ 178 protected SmackDebugger debugger = null; 179 180 /** 181 * The Reader which is used for the {@see debugger}. 182 */ 183 protected Reader reader; 184 185 /** 186 * The Writer which is used for the {@see debugger}. 187 */ 188 protected Writer writer; 189 190 /** 191 * The permanent storage for the roster 192 */ 193 protected RosterStorage rosterStorage; 194 195 196 /** 197 * The SASLAuthentication manager that is responsible for authenticating with the server. 198 */ 199 protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); 200 201 /** 202 * A number to uniquely identify connections that are created. This is distinct from the 203 * connection ID, which is a value sent by the server once a connection is made. 204 */ 205 protected final int connectionCounterValue = connectionCounter.getAndIncrement(); 206 207 /** 208 * Holds the initial configuration used while creating the connection. 209 */ 210 protected final ConnectionConfiguration config; 211 212 /** 213 * Holds the Caps Node information for the used XMPP service (i.e. the XMPP server) 214 */ 215 private String serviceCapsNode; 216 217 protected XMPPInputOutputStream compressionHandler; 218 219 /** 220 * Create a new Connection to a XMPP server. 221 * 222 * @param configuration The configuration which is used to establish the connection. 223 */ 224 protected Connection(ConnectionConfiguration configuration) { 225 config = configuration; 226 } 227 228 /** 229 * Returns the configuration used to connect to the server. 230 * 231 * @return the configuration used to connect to the server. 232 */ 233 protected ConnectionConfiguration getConfiguration() { 234 return config; 235 } 236 237 /** 238 * Returns the name of the service provided by the XMPP server for this connection. 239 * This is also called XMPP domain of the connected server. After 240 * authenticating with the server the returned value may be different. 241 * 242 * @return the name of the service provided by the XMPP server. 243 */ 244 public String getServiceName() { 245 return config.getServiceName(); 246 } 247 248 /** 249 * Returns the host name of the server where the XMPP server is running. This would be the 250 * IP address of the server or a name that may be resolved by a DNS server. 251 * 252 * @return the host name of the server where the XMPP server is running. 253 */ 254 public String getHost() { 255 return config.getHost(); 256 } 257 258 /** 259 * Returns the port number of the XMPP server for this connection. The default port 260 * for normal connections is 5222. The default port for SSL connections is 5223. 261 * 262 * @return the port number of the XMPP server. 263 */ 264 public int getPort() { 265 return config.getPort(); 266 } 267 268 /** 269 * Returns the full XMPP address of the user that is logged in to the connection or 270 * <tt>null</tt> if not logged in yet. An XMPP address is in the form 271 * username@server/resource. 272 * 273 * @return the full XMPP address of the user logged in. 274 */ 275 public abstract String getUser(); 276 277 /** 278 * Returns the connection ID for this connection, which is the value set by the server 279 * when opening a XMPP stream. If the server does not set a connection ID, this value 280 * will be null. This value will be <tt>null</tt> if not connected to the server. 281 * 282 * @return the ID of this connection returned from the XMPP server or <tt>null</tt> if 283 * not connected to the server. 284 */ 285 public abstract String getConnectionID(); 286 287 /** 288 * Returns true if currently connected to the XMPP server. 289 * 290 * @return true if connected. 291 */ 292 public abstract boolean isConnected(); 293 294 /** 295 * Returns true if currently authenticated by successfully calling the login method. 296 * 297 * @return true if authenticated. 298 */ 299 public abstract boolean isAuthenticated(); 300 301 /** 302 * Returns true if currently authenticated anonymously. 303 * 304 * @return true if authenticated anonymously. 305 */ 306 public abstract boolean isAnonymous(); 307 308 /** 309 * Returns true if the connection to the server has successfully negotiated encryption. 310 * 311 * @return true if a secure connection to the server. 312 */ 313 public abstract boolean isSecureConnection(); 314 315 /** 316 * Returns if the reconnection mechanism is allowed to be used. By default 317 * reconnection is allowed. 318 * 319 * @return true if the reconnection mechanism is allowed to be used. 320 */ 321 protected boolean isReconnectionAllowed() { 322 return config.isReconnectionAllowed(); 323 } 324 325 /** 326 * Returns true if network traffic is being compressed. When using stream compression network 327 * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow 328 * speed network connection. However, the server will need to use more CPU time in order to 329 * un/compress network data so under high load the server performance might be affected. 330 * 331 * @return true if network traffic is being compressed. 332 */ 333 public abstract boolean isUsingCompression(); 334 335 /** 336 * Establishes a connection to the XMPP server and performs an automatic login 337 * only if the previous connection state was logged (authenticated). It basically 338 * creates and maintains a connection to the server.<p> 339 * <p/> 340 * Listeners will be preserved from a previous connection if the reconnection 341 * occurs after an abrupt termination. 342 * 343 * @throws XMPPException if an error occurs while trying to establish the connection. 344 */ 345 public abstract void connect() throws XMPPException; 346 347 /** 348 * Logs in to the server using the strongest authentication mode supported by 349 * the server, then sets presence to available. If the server supports SASL authentication 350 * then the user will be authenticated using SASL if not Non-SASL authentication will 351 * be tried. If more than five seconds (default timeout) elapses in each step of the 352 * authentication process without a response from the server, or if an error occurs, a 353 * XMPPException will be thrown.<p> 354 * 355 * Before logging in (i.e. authenticate) to the server the connection must be connected. 356 * 357 * It is possible to log in without sending an initial available presence by using 358 * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is 359 * not interested in loading its roster upon login then use 360 * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. 361 * Finally, if you want to not pass a password and instead use a more advanced mechanism 362 * while using SASL then you may be interested in using 363 * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. 364 * For more advanced login settings see {@link ConnectionConfiguration}. 365 * 366 * @param username the username. 367 * @param password the password or <tt>null</tt> if using a CallbackHandler. 368 * @throws XMPPException if an error occurs. 369 */ 370 public void login(String username, String password) throws XMPPException { 371 login(username, password, "Smack"); 372 } 373 374 /** 375 * Logs in to the server using the strongest authentication mode supported by 376 * the server, then sets presence to available. If the server supports SASL authentication 377 * then the user will be authenticated using SASL if not Non-SASL authentication will 378 * be tried. If more than five seconds (default timeout) elapses in each step of the 379 * authentication process without a response from the server, or if an error occurs, a 380 * XMPPException will be thrown.<p> 381 * 382 * Before logging in (i.e. authenticate) to the server the connection must be connected. 383 * 384 * It is possible to log in without sending an initial available presence by using 385 * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is 386 * not interested in loading its roster upon login then use 387 * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. 388 * Finally, if you want to not pass a password and instead use a more advanced mechanism 389 * while using SASL then you may be interested in using 390 * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. 391 * For more advanced login settings see {@link ConnectionConfiguration}. 392 * 393 * @param username the username. 394 * @param password the password or <tt>null</tt> if using a CallbackHandler. 395 * @param resource the resource. 396 * @throws XMPPException if an error occurs. 397 * @throws IllegalStateException if not connected to the server, or already logged in 398 * to the serrver. 399 */ 400 public abstract void login(String username, String password, String resource) throws XMPPException; 401 402 /** 403 * Logs in to the server anonymously. Very few servers are configured to support anonymous 404 * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login 405 * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or 406 * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). 407 * 408 * @throws XMPPException if an error occurs or anonymous logins are not supported by the server. 409 * @throws IllegalStateException if not connected to the server, or already logged in 410 * to the serrver. 411 */ 412 public abstract void loginAnonymously() throws XMPPException; 413 414 /** 415 * Sends the specified packet to the server. 416 * 417 * @param packet the packet to send. 418 */ 419 public abstract void sendPacket(Packet packet); 420 421 /** 422 * Returns an account manager instance for this connection. 423 * 424 * @return an account manager for this connection. 425 */ 426 public AccountManager getAccountManager() { 427 if (accountManager == null) { 428 accountManager = new AccountManager(this); 429 } 430 return accountManager; 431 } 432 433 /** 434 * Returns a chat manager instance for this connection. The ChatManager manages all incoming and 435 * outgoing chats on the current connection. 436 * 437 * @return a chat manager instance for this connection. 438 */ 439 public synchronized ChatManager getChatManager() { 440 if (this.chatManager == null) { 441 this.chatManager = new ChatManager(this); 442 } 443 return this.chatManager; 444 } 445 446 /** 447 * Returns the roster for the user. 448 * <p> 449 * This method will never return <code>null</code>, instead if the user has not yet logged into 450 * the server or is logged in anonymously all modifying methods of the returned roster object 451 * like {@link Roster#createEntry(String, String, String[])}, 452 * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing 453 * {@link RosterListener}s will throw an IllegalStateException. 454 * 455 * @return the user's roster. 456 */ 457 public abstract Roster getRoster(); 458 459 /** 460 * Set the store for the roster of this connection. If you set the roster storage 461 * of a connection you enable support for XEP-0237 (RosterVersioning) 462 * @param store the store used for roster versioning 463 * @throws IllegalStateException if you add a roster store when roster is initializied 464 */ 465 public abstract void setRosterStorage(RosterStorage storage) throws IllegalStateException; 466 467 /** 468 * Returns the SASLAuthentication manager that is responsible for authenticating with 469 * the server. 470 * 471 * @return the SASLAuthentication manager that is responsible for authenticating with 472 * the server. 473 */ 474 public SASLAuthentication getSASLAuthentication() { 475 return saslAuthentication; 476 } 477 478 /** 479 * Closes the connection by setting presence to unavailable then closing the connection to 480 * the XMPP server. The Connection can still be used for connecting to the server 481 * again.<p> 482 * <p/> 483 * This method cleans up all resources used by the connection. Therefore, the roster, 484 * listeners and other stateful objects cannot be re-used by simply calling connect() 485 * on this connection again. This is unlike the behavior during unexpected disconnects 486 * (and subsequent connections). In that case, all state is preserved to allow for 487 * more seamless error recovery. 488 */ 489 public void disconnect() { 490 disconnect(new Presence(Presence.Type.unavailable)); 491 } 492 493 /** 494 * Closes the connection. A custom unavailable presence is sent to the server, followed 495 * by closing the stream. The Connection can still be used for connecting to the server 496 * again. A custom unavilable presence is useful for communicating offline presence 497 * information such as "On vacation". Typically, just the status text of the presence 498 * packet is set with online information, but most XMPP servers will deliver the full 499 * presence packet with whatever data is set.<p> 500 * <p/> 501 * This method cleans up all resources used by the connection. Therefore, the roster, 502 * listeners and other stateful objects cannot be re-used by simply calling connect() 503 * on this connection again. This is unlike the behavior during unexpected disconnects 504 * (and subsequent connections). In that case, all state is preserved to allow for 505 * more seamless error recovery. 506 * 507 * @param unavailablePresence the presence packet to send during shutdown. 508 */ 509 public abstract void disconnect(Presence unavailablePresence); 510 511 /** 512 * Adds a new listener that will be notified when new Connections are created. Note 513 * that newly created connections will not be actually connected to the server. 514 * 515 * @param connectionCreationListener a listener interested on new connections. 516 */ 517 public static void addConnectionCreationListener( 518 ConnectionCreationListener connectionCreationListener) { 519 connectionEstablishedListeners.add(connectionCreationListener); 520 } 521 522 /** 523 * Removes a listener that was interested in connection creation events. 524 * 525 * @param connectionCreationListener a listener interested on new connections. 526 */ 527 public static void removeConnectionCreationListener( 528 ConnectionCreationListener connectionCreationListener) { 529 connectionEstablishedListeners.remove(connectionCreationListener); 530 } 531 532 /** 533 * Get the collection of listeners that are interested in connection creation events. 534 * 535 * @return a collection of listeners interested on new connections. 536 */ 537 protected static Collection<ConnectionCreationListener> getConnectionCreationListeners() { 538 return Collections.unmodifiableCollection(connectionEstablishedListeners); 539 } 540 541 /** 542 * Adds a connection listener to this connection that will be notified when 543 * the connection closes or fails. The connection needs to already be connected 544 * or otherwise an IllegalStateException will be thrown. 545 * 546 * @param connectionListener a connection listener. 547 */ 548 public void addConnectionListener(ConnectionListener connectionListener) { 549 if (!isConnected()) { 550 throw new IllegalStateException("Not connected to server."); 551 } 552 if (connectionListener == null) { 553 return; 554 } 555 if (!connectionListeners.contains(connectionListener)) { 556 connectionListeners.add(connectionListener); 557 } 558 } 559 560 /** 561 * Removes a connection listener from this connection. 562 * 563 * @param connectionListener a connection listener. 564 */ 565 public void removeConnectionListener(ConnectionListener connectionListener) { 566 connectionListeners.remove(connectionListener); 567 } 568 569 /** 570 * Get the collection of listeners that are interested in connection events. 571 * 572 * @return a collection of listeners interested on connection events. 573 */ 574 protected Collection<ConnectionListener> getConnectionListeners() { 575 return connectionListeners; 576 } 577 578 /** 579 * Creates a new packet collector for this connection. A packet filter determines 580 * which packets will be accumulated by the collector. A PacketCollector is 581 * more suitable to use than a {@link PacketListener} when you need to wait for 582 * a specific result. 583 * 584 * @param packetFilter the packet filter to use. 585 * @return a new packet collector. 586 */ 587 public PacketCollector createPacketCollector(PacketFilter packetFilter) { 588 PacketCollector collector = new PacketCollector(this, packetFilter); 589 // Add the collector to the list of active collectors. 590 collectors.add(collector); 591 return collector; 592 } 593 594 /** 595 * Remove a packet collector of this connection. 596 * 597 * @param collector a packet collectors which was created for this connection. 598 */ 599 protected void removePacketCollector(PacketCollector collector) { 600 collectors.remove(collector); 601 } 602 603 /** 604 * Get the collection of all packet collectors for this connection. 605 * 606 * @return a collection of packet collectors for this connection. 607 */ 608 protected Collection<PacketCollector> getPacketCollectors() { 609 return collectors; 610 } 611 612 /** 613 * Registers a packet listener with this connection. A packet filter determines 614 * which packets will be delivered to the listener. If the same packet listener 615 * is added again with a different filter, only the new filter will be used. 616 * 617 * @param packetListener the packet listener to notify of new received packets. 618 * @param packetFilter the packet filter to use. 619 */ 620 public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { 621 if (packetListener == null) { 622 throw new NullPointerException("Packet listener is null."); 623 } 624 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 625 recvListeners.put(packetListener, wrapper); 626 } 627 628 /** 629 * Removes a packet listener for received packets from this connection. 630 * 631 * @param packetListener the packet listener to remove. 632 */ 633 public void removePacketListener(PacketListener packetListener) { 634 recvListeners.remove(packetListener); 635 } 636 637 /** 638 * Get a map of all packet listeners for received packets of this connection. 639 * 640 * @return a map of all packet listeners for received packets. 641 */ 642 protected Map<PacketListener, ListenerWrapper> getPacketListeners() { 643 return recvListeners; 644 } 645 646 /** 647 * Registers a packet listener with this connection. The listener will be 648 * notified of every packet that this connection sends. A packet filter determines 649 * which packets will be delivered to the listener. Note that the thread 650 * that writes packets will be used to invoke the listeners. Therefore, each 651 * packet listener should complete all operations quickly or use a different 652 * thread for processing. 653 * 654 * @param packetListener the packet listener to notify of sent packets. 655 * @param packetFilter the packet filter to use. 656 */ 657 public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { 658 if (packetListener == null) { 659 throw new NullPointerException("Packet listener is null."); 660 } 661 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 662 sendListeners.put(packetListener, wrapper); 663 } 664 665 /** 666 * Removes a packet listener for sending packets from this connection. 667 * 668 * @param packetListener the packet listener to remove. 669 */ 670 public void removePacketSendingListener(PacketListener packetListener) { 671 sendListeners.remove(packetListener); 672 } 673 674 /** 675 * Get a map of all packet listeners for sending packets of this connection. 676 * 677 * @return a map of all packet listeners for sent packets. 678 */ 679 protected Map<PacketListener, ListenerWrapper> getPacketSendingListeners() { 680 return sendListeners; 681 } 682 683 684 /** 685 * Process all packet listeners for sending packets. 686 * 687 * @param packet the packet to process. 688 */ 689 protected void firePacketSendingListeners(Packet packet) { 690 // Notify the listeners of the new sent packet 691 for (ListenerWrapper listenerWrapper : sendListeners.values()) { 692 listenerWrapper.notifyListener(packet); 693 } 694 } 695 696 /** 697 * Registers a packet interceptor with this connection. The interceptor will be 698 * invoked every time a packet is about to be sent by this connection. Interceptors 699 * may modify the packet to be sent. A packet filter determines which packets 700 * will be delivered to the interceptor. 701 * 702 * @param packetInterceptor the packet interceptor to notify of packets about to be sent. 703 * @param packetFilter the packet filter to use. 704 */ 705 public void addPacketInterceptor(PacketInterceptor packetInterceptor, 706 PacketFilter packetFilter) { 707 if (packetInterceptor == null) { 708 throw new NullPointerException("Packet interceptor is null."); 709 } 710 interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); 711 } 712 713 /** 714 * Removes a packet interceptor. 715 * 716 * @param packetInterceptor the packet interceptor to remove. 717 */ 718 public void removePacketInterceptor(PacketInterceptor packetInterceptor) { 719 interceptors.remove(packetInterceptor); 720 } 721 722 public boolean isSendPresence() { 723 return config.isSendPresence(); 724 } 725 726 /** 727 * Get a map of all packet interceptors for sending packets of this connection. 728 * 729 * @return a map of all packet interceptors for sending packets. 730 */ 731 protected Map<PacketInterceptor, InterceptorWrapper> getPacketInterceptors() { 732 return interceptors; 733 } 734 735 /** 736 * Process interceptors. Interceptors may modify the packet that is about to be sent. 737 * Since the thread that requested to send the packet will invoke all interceptors, it 738 * is important that interceptors perform their work as soon as possible so that the 739 * thread does not remain blocked for a long period. 740 * 741 * @param packet the packet that is going to be sent to the server 742 */ 743 protected void firePacketInterceptors(Packet packet) { 744 if (packet != null) { 745 for (InterceptorWrapper interceptorWrapper : interceptors.values()) { 746 interceptorWrapper.notifyListener(packet); 747 } 748 } 749 } 750 751 /** 752 * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} 753 * by setup the system property <code>smack.debuggerClass</code> to the implementation. 754 * 755 * @throws IllegalStateException if the reader or writer isn't yet initialized. 756 * @throws IllegalArgumentException if the SmackDebugger can't be loaded. 757 */ 758 protected void initDebugger() { 759 if (reader == null || writer == null) { 760 throw new NullPointerException("Reader or writer isn't initialized."); 761 } 762 // If debugging is enabled, we open a window and write out all network traffic. 763 if (config.isDebuggerEnabled()) { 764 if (debugger == null) { 765 // Detect the debugger class to use. 766 String className = null; 767 // Use try block since we may not have permission to get a system 768 // property (for example, when an applet). 769 try { 770 className = System.getProperty("smack.debuggerClass"); 771 } 772 catch (Throwable t) { 773 // Ignore. 774 } 775 Class<?> debuggerClass = null; 776 if (className != null) { 777 try { 778 debuggerClass = Class.forName(className); 779 } 780 catch (Exception e) { 781 e.printStackTrace(); 782 } 783 } 784 if (debuggerClass == null) { 785 try { 786 debuggerClass = 787 Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); 788 } 789 catch (Exception ex) { 790 try { 791 debuggerClass = 792 Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); 793 } 794 catch (Exception ex2) { 795 ex2.printStackTrace(); 796 } 797 } 798 } 799 // Create a new debugger instance. If an exception occurs then disable the debugging 800 // option 801 try { 802 Constructor<?> constructor = debuggerClass 803 .getConstructor(Connection.class, Writer.class, Reader.class); 804 debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); 805 reader = debugger.getReader(); 806 writer = debugger.getWriter(); 807 } 808 catch (Exception e) { 809 throw new IllegalArgumentException("Can't initialize the configured debugger!", e); 810 } 811 } 812 else { 813 // Obtain new reader and writer from the existing debugger 814 reader = debugger.newConnectionReader(reader); 815 writer = debugger.newConnectionWriter(writer); 816 } 817 } 818 819 } 820 821 /** 822 * Set the servers Entity Caps node 823 * 824 * Connection holds this information in order to avoid a dependency to 825 * smackx where EntityCapsManager lives from smack. 826 * 827 * @param node 828 */ 829 protected void setServiceCapsNode(String node) { 830 serviceCapsNode = node; 831 } 832 833 /** 834 * Retrieve the servers Entity Caps node 835 * 836 * Connection holds this information in order to avoid a dependency to 837 * smackx where EntityCapsManager lives from smack. 838 * 839 * @return 840 */ 841 public String getServiceCapsNode() { 842 return serviceCapsNode; 843 } 844 845 /** 846 * A wrapper class to associate a packet filter with a listener. 847 */ 848 protected static class ListenerWrapper { 849 850 private PacketListener packetListener; 851 private PacketFilter packetFilter; 852 853 /** 854 * Create a class which associates a packet filter with a listener. 855 * 856 * @param packetListener the packet listener. 857 * @param packetFilter the associated filter or null if it listen for all packets. 858 */ 859 public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { 860 this.packetListener = packetListener; 861 this.packetFilter = packetFilter; 862 } 863 864 /** 865 * Notify and process the packet listener if the filter matches the packet. 866 * 867 * @param packet the packet which was sent or received. 868 */ 869 public void notifyListener(Packet packet) { 870 if (packetFilter == null || packetFilter.accept(packet)) { 871 packetListener.processPacket(packet); 872 } 873 } 874 } 875 876 /** 877 * A wrapper class to associate a packet filter with an interceptor. 878 */ 879 protected static class InterceptorWrapper { 880 881 private PacketInterceptor packetInterceptor; 882 private PacketFilter packetFilter; 883 884 /** 885 * Create a class which associates a packet filter with an interceptor. 886 * 887 * @param packetInterceptor the interceptor. 888 * @param packetFilter the associated filter or null if it intercepts all packets. 889 */ 890 public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { 891 this.packetInterceptor = packetInterceptor; 892 this.packetFilter = packetFilter; 893 } 894 895 public boolean equals(Object object) { 896 if (object == null) { 897 return false; 898 } 899 if (object instanceof InterceptorWrapper) { 900 return ((InterceptorWrapper) object).packetInterceptor 901 .equals(this.packetInterceptor); 902 } 903 else if (object instanceof PacketInterceptor) { 904 return object.equals(this.packetInterceptor); 905 } 906 return false; 907 } 908 909 /** 910 * Notify and process the packet interceptor if the filter matches the packet. 911 * 912 * @param packet the packet which will be sent. 913 */ 914 public void notifyListener(Packet packet) { 915 if (packetFilter == null || packetFilter.accept(packet)) { 916 packetInterceptor.interceptPacket(packet); 917 } 918 } 919 } 920} 921