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.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.LinkedHashMap; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.ConcurrentLinkedQueue; 031import java.util.concurrent.CopyOnWriteArraySet; 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.Executors; 034import java.util.concurrent.ScheduledExecutorService; 035import java.util.concurrent.ScheduledFuture; 036import java.util.concurrent.TimeUnit; 037import java.util.concurrent.atomic.AtomicInteger; 038import java.util.concurrent.locks.Lock; 039import java.util.concurrent.locks.ReentrantLock; 040import java.util.logging.Level; 041import java.util.logging.Logger; 042 043import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; 044import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode; 045import org.jivesoftware.smack.SmackException.AlreadyConnectedException; 046import org.jivesoftware.smack.SmackException.AlreadyLoggedInException; 047import org.jivesoftware.smack.SmackException.NoResponseException; 048import org.jivesoftware.smack.SmackException.NotConnectedException; 049import org.jivesoftware.smack.SmackException.NotLoggedInException; 050import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException; 051import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException; 052import org.jivesoftware.smack.SmackException.SecurityRequiredException; 053import org.jivesoftware.smack.XMPPException.StreamErrorException; 054import org.jivesoftware.smack.XMPPException.XMPPErrorException; 055import org.jivesoftware.smack.compress.packet.Compress; 056import org.jivesoftware.smack.compression.XMPPInputOutputStream; 057import org.jivesoftware.smack.debugger.SmackDebugger; 058import org.jivesoftware.smack.filter.IQReplyFilter; 059import org.jivesoftware.smack.filter.StanzaFilter; 060import org.jivesoftware.smack.filter.StanzaIdFilter; 061import org.jivesoftware.smack.iqrequest.IQRequestHandler; 062import org.jivesoftware.smack.packet.Bind; 063import org.jivesoftware.smack.packet.ErrorIQ; 064import org.jivesoftware.smack.packet.ExtensionElement; 065import org.jivesoftware.smack.packet.IQ; 066import org.jivesoftware.smack.packet.Mechanisms; 067import org.jivesoftware.smack.packet.Message; 068import org.jivesoftware.smack.packet.Nonza; 069import org.jivesoftware.smack.packet.Presence; 070import org.jivesoftware.smack.packet.Session; 071import org.jivesoftware.smack.packet.Stanza; 072import org.jivesoftware.smack.packet.StartTls; 073import org.jivesoftware.smack.packet.StreamError; 074import org.jivesoftware.smack.packet.XMPPError; 075import org.jivesoftware.smack.parsing.ParsingExceptionCallback; 076import org.jivesoftware.smack.provider.ExtensionElementProvider; 077import org.jivesoftware.smack.provider.ProviderManager; 078import org.jivesoftware.smack.sasl.core.SASLAnonymous; 079import org.jivesoftware.smack.util.BoundedThreadPoolExecutor; 080import org.jivesoftware.smack.util.DNSUtil; 081import org.jivesoftware.smack.util.Objects; 082import org.jivesoftware.smack.util.PacketParserUtils; 083import org.jivesoftware.smack.util.ParserUtils; 084import org.jivesoftware.smack.util.SmackExecutorThreadFactory; 085import org.jivesoftware.smack.util.StringUtils; 086import org.jivesoftware.smack.util.dns.HostAddress; 087 088import org.jxmpp.jid.DomainBareJid; 089import org.jxmpp.jid.EntityFullJid; 090import org.jxmpp.jid.Jid; 091import org.jxmpp.jid.parts.Resourcepart; 092import org.jxmpp.util.XmppStringUtils; 093import org.xmlpull.v1.XmlPullParser; 094 095 096public abstract class AbstractXMPPConnection implements XMPPConnection { 097 private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName()); 098 099 /** 100 * Counter to uniquely identify connections that are created. 101 */ 102 private final static AtomicInteger connectionCounter = new AtomicInteger(0); 103 104 static { 105 // Ensure the SmackConfiguration class is loaded by calling a method in it. 106 SmackConfiguration.getVersion(); 107 } 108 109 /** 110 * A collection of ConnectionListeners which listen for connection closing 111 * and reconnection events. 112 */ 113 protected final Set<ConnectionListener> connectionListeners = 114 new CopyOnWriteArraySet<ConnectionListener>(); 115 116 /** 117 * A collection of StanzaCollectors which collects packets for a specified filter 118 * and perform blocking and polling operations on the result queue. 119 * <p> 120 * We use a ConcurrentLinkedQueue here, because its Iterator is weakly 121 * consistent and we want {@link #invokeStanzaCollectorsAndNotifyRecvListeners(Stanza)} for-each 122 * loop to be lock free. As drawback, removing a StanzaCollector is O(n). 123 * The alternative would be a synchronized HashSet, but this would mean a 124 * synchronized block around every usage of <code>collectors</code>. 125 * </p> 126 */ 127 private final Collection<StanzaCollector> collectors = new ConcurrentLinkedQueue<>(); 128 129 /** 130 * List of PacketListeners that will be notified synchronously when a new stanza(/packet) was received. 131 */ 132 private final Map<StanzaListener, ListenerWrapper> syncRecvListeners = new LinkedHashMap<>(); 133 134 /** 135 * List of PacketListeners that will be notified asynchronously when a new stanza(/packet) was received. 136 */ 137 private final Map<StanzaListener, ListenerWrapper> asyncRecvListeners = new LinkedHashMap<>(); 138 139 /** 140 * List of PacketListeners that will be notified when a new stanza(/packet) was sent. 141 */ 142 private final Map<StanzaListener, ListenerWrapper> sendListeners = 143 new HashMap<StanzaListener, ListenerWrapper>(); 144 145 /** 146 * List of PacketListeners that will be notified when a new stanza(/packet) is about to be 147 * sent to the server. These interceptors may modify the stanza(/packet) before it is being 148 * actually sent to the server. 149 */ 150 private final Map<StanzaListener, InterceptorWrapper> interceptors = 151 new HashMap<StanzaListener, InterceptorWrapper>(); 152 153 protected final Lock connectionLock = new ReentrantLock(); 154 155 protected final Map<String, ExtensionElement> streamFeatures = new HashMap<String, ExtensionElement>(); 156 157 /** 158 * The full JID of the authenticated user, as returned by the resource binding response of the server. 159 * <p> 160 * It is important that we don't infer the user from the login() arguments and the configurations service name, as, 161 * for example, when SASL External is used, the username is not given to login but taken from the 'external' 162 * certificate. 163 * </p> 164 */ 165 protected EntityFullJid user; 166 167 protected boolean connected = false; 168 169 /** 170 * The stream ID, see RFC 6120 § 4.7.3 171 */ 172 protected String streamId; 173 174 /** 175 * The timeout to wait for a reply in milliseconds. 176 */ 177 private long replyTimeout = SmackConfiguration.getDefaultReplyTimeout(); 178 179 /** 180 * The SmackDebugger allows to log and debug XML traffic. 181 */ 182 protected SmackDebugger debugger = null; 183 184 /** 185 * The Reader which is used for the debugger. 186 */ 187 protected Reader reader; 188 189 /** 190 * The Writer which is used for the debugger. 191 */ 192 protected Writer writer; 193 194 protected final SynchronizationPoint<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS"); 195 196 /** 197 * Set to success if the last features stanza from the server has been parsed. A XMPP connection 198 * handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature 199 * stanza is send by the server. This is set to true once the last feature stanza has been 200 * parsed. 201 */ 202 protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>( 203 AbstractXMPPConnection.this, "last stream features received from server"); 204 205 /** 206 * Set to success if the SASL feature has been received. 207 */ 208 protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>( 209 AbstractXMPPConnection.this, "SASL mechanisms stream feature from server"); 210 211 /** 212 * The SASLAuthentication manager that is responsible for authenticating with the server. 213 */ 214 protected final SASLAuthentication saslAuthentication; 215 216 /** 217 * A number to uniquely identify connections that are created. This is distinct from the 218 * connection ID, which is a value sent by the server once a connection is made. 219 */ 220 protected final int connectionCounterValue = connectionCounter.getAndIncrement(); 221 222 /** 223 * Holds the initial configuration used while creating the connection. 224 */ 225 protected final ConnectionConfiguration config; 226 227 /** 228 * Defines how the from attribute of outgoing stanzas should be handled. 229 */ 230 private FromMode fromMode = FromMode.OMITTED; 231 232 protected XMPPInputOutputStream compressionHandler; 233 234 private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback(); 235 236 /** 237 * ExecutorService used to invoke the PacketListeners on newly arrived and parsed stanzas. It is 238 * important that we use a <b>single threaded ExecutorService</b> in order to guarantee that the 239 * PacketListeners are invoked in the same order the stanzas arrived. 240 */ 241 private final BoundedThreadPoolExecutor executorService = new BoundedThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, 242 100, new SmackExecutorThreadFactory(this, "Incoming Processor")); 243 244 /** 245 * This scheduled thread pool executor is used to remove pending callbacks. 246 */ 247 private final ScheduledExecutorService removeCallbacksService = Executors.newSingleThreadScheduledExecutor( 248 new SmackExecutorThreadFactory(this, "Remove Callbacks")); 249 250 /** 251 * A cached thread pool executor service with custom thread factory to set meaningful names on the threads and set 252 * them 'daemon'. 253 */ 254 private final ExecutorService cachedExecutorService = Executors.newCachedThreadPool( 255 // @formatter:off 256 // CHECKSTYLE:OFF 257 new SmackExecutorThreadFactory( // threadFactory 258 this, 259 "Cached Executor" 260 ) 261 // @formatter:on 262 // CHECKSTYLE:ON 263 ); 264 265 /** 266 * A executor service used to invoke the callbacks of synchronous stanza(/packet) listeners. We use a executor service to 267 * decouple incoming stanza processing from callback invocation. It is important that order of callback invocation 268 * is the same as the order of the incoming stanzas. Therefore we use a <i>single</i> threaded executor service. 269 */ 270 private final ExecutorService singleThreadedExecutorService = Executors.newSingleThreadExecutor(new SmackExecutorThreadFactory( 271 this, "Single Threaded Executor")); 272 273 /** 274 * The used host to establish the connection to 275 */ 276 protected String host; 277 278 /** 279 * The used port to establish the connection to 280 */ 281 protected int port; 282 283 /** 284 * Flag that indicates if the user is currently authenticated with the server. 285 */ 286 protected boolean authenticated = false; 287 288 /** 289 * Flag that indicates if the user was authenticated with the server when the connection 290 * to the server was closed (abruptly or not). 291 */ 292 protected boolean wasAuthenticated = false; 293 294 private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>(); 295 private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>(); 296 297 /** 298 * Create a new XMPPConnection to an XMPP server. 299 * 300 * @param configuration The configuration which is used to establish the connection. 301 */ 302 protected AbstractXMPPConnection(ConnectionConfiguration configuration) { 303 saslAuthentication = new SASLAuthentication(this, configuration); 304 config = configuration; 305 // Notify listeners that a new connection has been established 306 for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) { 307 listener.connectionCreated(this); 308 } 309 } 310 311 /** 312 * Get the connection configuration used by this connection. 313 * 314 * @return the connection configuration. 315 */ 316 public ConnectionConfiguration getConfiguration() { 317 return config; 318 } 319 320 @SuppressWarnings("deprecation") 321 @Override 322 public DomainBareJid getServiceName() { 323 return getXMPPServiceDomain(); 324 } 325 326 @Override 327 public DomainBareJid getXMPPServiceDomain() { 328 if (xmppServiceDomain != null) { 329 return xmppServiceDomain; 330 } 331 return config.getXMPPServiceDomain(); 332 } 333 334 @Override 335 public String getHost() { 336 return host; 337 } 338 339 @Override 340 public int getPort() { 341 return port; 342 } 343 344 @Override 345 public abstract boolean isSecureConnection(); 346 347 protected abstract void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException; 348 349 @Override 350 public abstract void sendNonza(Nonza element) throws NotConnectedException, InterruptedException; 351 352 @Override 353 public abstract boolean isUsingCompression(); 354 355 /** 356 * Establishes a connection to the XMPP server. It basically 357 * creates and maintains a connection to the server. 358 * <p> 359 * Listeners will be preserved from a previous connection. 360 * </p> 361 * 362 * @throws XMPPException if an error occurs on the XMPP protocol level. 363 * @throws SmackException if an error occurs somewhere else besides XMPP protocol level. 364 * @throws IOException 365 * @return a reference to this object, to chain <code>connect()</code> with <code>login()</code>. 366 * @throws InterruptedException 367 */ 368 public synchronized AbstractXMPPConnection connect() throws SmackException, IOException, XMPPException, InterruptedException { 369 // Check if not already connected 370 throwAlreadyConnectedExceptionIfAppropriate(); 371 372 // Reset the connection state 373 saslAuthentication.init(); 374 saslFeatureReceived.init(); 375 lastFeaturesReceived.init(); 376 tlsHandled.init(); 377 streamId = null; 378 379 // Perform the actual connection to the XMPP service 380 connectInternal(); 381 382 // TLS handled will be successful either if TLS was established, or if it was not mandatory. 383 tlsHandled.checkIfSuccessOrWaitOrThrow(); 384 385 // Wait with SASL auth until the SASL mechanisms have been received 386 saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); 387 388 // If TLS is required but the server doesn't offer it, disconnect 389 // from the server and throw an error. First check if we've already negotiated TLS 390 // and are secure, however (features get parsed a second time after TLS is established). 391 if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) { 392 shutdown(); 393 throw new SecurityRequiredByClientException(); 394 } 395 396 // Make note of the fact that we're now connected. 397 connected = true; 398 callConnectionConnectedListener(); 399 400 return this; 401 } 402 403 /** 404 * Abstract method that concrete subclasses of XMPPConnection need to implement to perform their 405 * way of XMPP connection establishment. Implementations are required to perform an automatic 406 * login if the previous connection state was logged (authenticated). 407 * 408 * @throws SmackException 409 * @throws IOException 410 * @throws XMPPException 411 * @throws InterruptedException 412 */ 413 protected abstract void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException; 414 415 private String usedUsername, usedPassword; 416 417 /** 418 * The resourcepart used for this connection. May not be the resulting resourcepart if it's null or overridden by the XMPP service. 419 */ 420 private Resourcepart usedResource; 421 422 /** 423 * Logs in to the server using the strongest SASL mechanism supported by 424 * the server. If more than the connection's default stanza(/packet) timeout elapses in each step of the 425 * authentication process without a response from the server, a 426 * {@link SmackException.NoResponseException} will be thrown. 427 * <p> 428 * Before logging in (i.e. authenticate) to the server the connection must be connected 429 * by calling {@link #connect}. 430 * </p> 431 * <p> 432 * It is possible to log in without sending an initial available presence by using 433 * {@link ConnectionConfiguration.Builder#setSendPresence(boolean)}. 434 * Finally, if you want to not pass a password and instead use a more advanced mechanism 435 * while using SASL then you may be interested in using 436 * {@link ConnectionConfiguration.Builder#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. 437 * For more advanced login settings see {@link ConnectionConfiguration}. 438 * </p> 439 * 440 * @throws XMPPException if an error occurs on the XMPP protocol level. 441 * @throws SmackException if an error occurs somewhere else besides XMPP protocol level. 442 * @throws IOException if an I/O error occurs during login. 443 * @throws InterruptedException 444 */ 445 public synchronized void login() throws XMPPException, SmackException, IOException, InterruptedException { 446 // The previously used username, password and resource take over precedence over the 447 // ones from the connection configuration 448 CharSequence username = usedUsername != null ? usedUsername : config.getUsername(); 449 String password = usedPassword != null ? usedPassword : config.getPassword(); 450 Resourcepart resource = usedResource != null ? usedResource : config.getResource(); 451 login(username, password, resource); 452 } 453 454 /** 455 * Same as {@link #login(CharSequence, String, Resourcepart)}, but takes the resource from the connection 456 * configuration. 457 * 458 * @param username 459 * @param password 460 * @throws XMPPException 461 * @throws SmackException 462 * @throws IOException 463 * @throws InterruptedException 464 * @see #login 465 */ 466 public synchronized void login(CharSequence username, String password) throws XMPPException, SmackException, 467 IOException, InterruptedException { 468 login(username, password, config.getResource()); 469 } 470 471 /** 472 * Login with the given username (authorization identity). You may omit the password if a callback handler is used. 473 * If resource is null, then the server will generate one. 474 * 475 * @param username 476 * @param password 477 * @param resource 478 * @throws XMPPException 479 * @throws SmackException 480 * @throws IOException 481 * @throws InterruptedException 482 * @see #login 483 */ 484 public synchronized void login(CharSequence username, String password, Resourcepart resource) throws XMPPException, 485 SmackException, IOException, InterruptedException { 486 if (!config.allowNullOrEmptyUsername) { 487 StringUtils.requireNotNullOrEmpty(username, "Username must not be null or empty"); 488 } 489 throwNotConnectedExceptionIfAppropriate("Did you call connect() before login()?"); 490 throwAlreadyLoggedInExceptionIfAppropriate(); 491 usedUsername = username != null ? username.toString() : null; 492 usedPassword = password; 493 usedResource = resource; 494 loginInternal(usedUsername, usedPassword, usedResource); 495 } 496 497 protected abstract void loginInternal(String username, String password, Resourcepart resource) 498 throws XMPPException, SmackException, IOException, InterruptedException; 499 500 @Override 501 public final boolean isConnected() { 502 return connected; 503 } 504 505 @Override 506 public final boolean isAuthenticated() { 507 return authenticated; 508 } 509 510 @Override 511 public final EntityFullJid getUser() { 512 return user; 513 } 514 515 @Override 516 public String getStreamId() { 517 if (!isConnected()) { 518 return null; 519 } 520 return streamId; 521 } 522 523 protected void bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException, 524 SmackException, InterruptedException { 525 526 // Wait until either: 527 // - the servers last features stanza has been parsed 528 // - the timeout occurs 529 LOGGER.finer("Waiting for last features to be received before continuing with resource binding"); 530 lastFeaturesReceived.checkIfSuccessOrWait(); 531 532 533 if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) { 534 // Server never offered resource binding, which is REQURIED in XMPP client and 535 // server implementations as per RFC6120 7.2 536 throw new ResourceBindingNotOfferedException(); 537 } 538 539 // Resource binding, see RFC6120 7. 540 // Note that we can not use IQReplyFilter here, since the users full JID is not yet 541 // available. It will become available right after the resource has been successfully bound. 542 Bind bindResource = Bind.newSet(resource); 543 StanzaCollector packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(bindResource), bindResource); 544 Bind response = packetCollector.nextResultOrThrow(); 545 // Set the connections user to the result of resource binding. It is important that we don't infer the user 546 // from the login() arguments and the configurations service name, as, for example, when SASL External is used, 547 // the username is not given to login but taken from the 'external' certificate. 548 user = response.getJid(); 549 xmppServiceDomain = user.asDomainBareJid(); 550 551 Session.Feature sessionFeature = getFeature(Session.ELEMENT, Session.NAMESPACE); 552 // Only bind the session if it's announced as stream feature by the server, is not optional and not disabled 553 // For more information see http://tools.ietf.org/html/draft-cridland-xmpp-session-01 554 // TODO remove this suppression once "disable legacy session" code has been removed from Smack 555 @SuppressWarnings("deprecation") 556 boolean legacySessionDisabled = getConfiguration().isLegacySessionDisabled(); 557 if (sessionFeature != null && !sessionFeature.isOptional() && !legacySessionDisabled) { 558 Session session = new Session(); 559 packetCollector = createStanzaCollectorAndSend(new StanzaIdFilter(session), session); 560 packetCollector.nextResultOrThrow(); 561 } 562 } 563 564 protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException { 565 // Indicate that we're now authenticated. 566 this.authenticated = true; 567 568 // If debugging is enabled, change the the debug window title to include the 569 // name we are now logged-in as. 570 // If DEBUG was set to true AFTER the connection was created the debugger 571 // will be null 572 if (config.isDebuggerEnabled() && debugger != null) { 573 debugger.userHasLogged(user); 574 } 575 callConnectionAuthenticatedListener(resumed); 576 577 // Set presence to online. It is important that this is done after 578 // callConnectionAuthenticatedListener(), as this call will also 579 // eventually load the roster. And we should load the roster before we 580 // send the initial presence. 581 if (config.isSendPresence() && !resumed) { 582 sendStanza(new Presence(Presence.Type.available)); 583 } 584 } 585 586 @Override 587 public final boolean isAnonymous() { 588 return isAuthenticated() && SASLAnonymous.NAME.equals(getUsedSaslMechansism()); 589 } 590 591 /** 592 * Get the name of the SASL mechanism that was used to authenticate this connection. This returns the name of 593 * mechanism which was used the last time this conneciton was authenticated, and will return <code>null</code> if 594 * this connection was not authenticated before. 595 * 596 * @return the name of the used SASL mechanism. 597 * @since 4.2 598 */ 599 public final String getUsedSaslMechansism() { 600 return saslAuthentication.getNameOfLastUsedSaslMechansism(); 601 } 602 603 private DomainBareJid xmppServiceDomain; 604 605 protected List<HostAddress> hostAddresses; 606 607 /** 608 * Populates {@link #hostAddresses} with the resolved addresses or with the configured host address. If no host 609 * address was configured and all lookups failed, for example with NX_DOMAIN, then {@link #hostAddresses} will be 610 * populated with the empty list. 611 * 612 * @return a list of host addresses where DNS (SRV) RR resolution failed. 613 */ 614 protected List<HostAddress> populateHostAddresses() { 615 List<HostAddress> failedAddresses = new LinkedList<>(); 616 if (config.hostAddress != null) { 617 hostAddresses = new ArrayList<>(1); 618 HostAddress hostAddress = new HostAddress(config.port, config.hostAddress); 619 hostAddresses.add(hostAddress); 620 } 621 else if (config.host != null) { 622 hostAddresses = new ArrayList<HostAddress>(1); 623 HostAddress hostAddress = DNSUtil.getDNSResolver().lookupHostAddress(config.host, config.port, failedAddresses, config.getDnssecMode()); 624 if (hostAddress != null) { 625 hostAddresses.add(hostAddress); 626 } 627 } else { 628 // N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName 629 hostAddresses = DNSUtil.resolveXMPPServiceDomain(config.getXMPPServiceDomain().toString(), failedAddresses, config.getDnssecMode()); 630 } 631 // Either the populated host addresses are not empty *or* there must be at least one failed address. 632 assert (!hostAddresses.isEmpty() || !failedAddresses.isEmpty()); 633 return failedAddresses; 634 } 635 636 protected Lock getConnectionLock() { 637 return connectionLock; 638 } 639 640 protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException { 641 throwNotConnectedExceptionIfAppropriate(null); 642 } 643 644 protected void throwNotConnectedExceptionIfAppropriate(String optionalHint) throws NotConnectedException { 645 if (!isConnected()) { 646 throw new NotConnectedException(optionalHint); 647 } 648 } 649 650 protected void throwAlreadyConnectedExceptionIfAppropriate() throws AlreadyConnectedException { 651 if (isConnected()) { 652 throw new AlreadyConnectedException(); 653 } 654 } 655 656 protected void throwAlreadyLoggedInExceptionIfAppropriate() throws AlreadyLoggedInException { 657 if (isAuthenticated()) { 658 throw new AlreadyLoggedInException(); 659 } 660 } 661 662 @Deprecated 663 @Override 664 public void sendPacket(Stanza packet) throws NotConnectedException, InterruptedException { 665 sendStanza(packet); 666 } 667 668 @Override 669 public void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException { 670 Objects.requireNonNull(stanza, "Stanza must not be null"); 671 assert (stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ); 672 673 throwNotConnectedExceptionIfAppropriate(); 674 switch (fromMode) { 675 case OMITTED: 676 stanza.setFrom((Jid) null); 677 break; 678 case USER: 679 stanza.setFrom(getUser()); 680 break; 681 case UNCHANGED: 682 default: 683 break; 684 } 685 // Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify 686 // the content of the stanza. 687 firePacketInterceptors(stanza); 688 sendStanzaInternal(stanza); 689 } 690 691 /** 692 * Returns the SASLAuthentication manager that is responsible for authenticating with 693 * the server. 694 * 695 * @return the SASLAuthentication manager that is responsible for authenticating with 696 * the server. 697 */ 698 protected SASLAuthentication getSASLAuthentication() { 699 return saslAuthentication; 700 } 701 702 /** 703 * Closes the connection by setting presence to unavailable then closing the connection to 704 * the XMPP server. The XMPPConnection can still be used for connecting to the server 705 * again. 706 * 707 */ 708 public void disconnect() { 709 try { 710 disconnect(new Presence(Presence.Type.unavailable)); 711 } 712 catch (NotConnectedException e) { 713 LOGGER.log(Level.FINEST, "Connection is already disconnected", e); 714 } 715 } 716 717 /** 718 * Closes the connection. A custom unavailable presence is sent to the server, followed 719 * by closing the stream. The XMPPConnection can still be used for connecting to the server 720 * again. A custom unavailable presence is useful for communicating offline presence 721 * information such as "On vacation". Typically, just the status text of the presence 722 * stanza(/packet) is set with online information, but most XMPP servers will deliver the full 723 * presence stanza(/packet) with whatever data is set. 724 * 725 * @param unavailablePresence the presence stanza(/packet) to send during shutdown. 726 * @throws NotConnectedException 727 */ 728 public synchronized void disconnect(Presence unavailablePresence) throws NotConnectedException { 729 try { 730 sendStanza(unavailablePresence); 731 } 732 catch (InterruptedException e) { 733 LOGGER.log(Level.FINE, "Was interrupted while sending unavailable presence. Continuing to disconnect the connection", e); 734 } 735 shutdown(); 736 callConnectionClosedListener(); 737 } 738 739 /** 740 * Shuts the current connection down. 741 */ 742 protected abstract void shutdown(); 743 744 @Override 745 public void addConnectionListener(ConnectionListener connectionListener) { 746 if (connectionListener == null) { 747 return; 748 } 749 connectionListeners.add(connectionListener); 750 } 751 752 @Override 753 public void removeConnectionListener(ConnectionListener connectionListener) { 754 connectionListeners.remove(connectionListener); 755 } 756 757 @Override 758 public StanzaCollector createStanzaCollectorAndSend(IQ packet) throws NotConnectedException, InterruptedException { 759 StanzaFilter packetFilter = new IQReplyFilter(packet, this); 760 // Create the packet collector before sending the packet 761 StanzaCollector packetCollector = createStanzaCollectorAndSend(packetFilter, packet); 762 return packetCollector; 763 } 764 765 @Override 766 public StanzaCollector createStanzaCollectorAndSend(StanzaFilter packetFilter, Stanza packet) 767 throws NotConnectedException, InterruptedException { 768 // Create the packet collector before sending the packet 769 StanzaCollector packetCollector = createStanzaCollector(packetFilter); 770 try { 771 // Now we can send the packet as the collector has been created 772 sendStanza(packet); 773 } 774 catch (InterruptedException | NotConnectedException | RuntimeException e) { 775 packetCollector.cancel(); 776 throw e; 777 } 778 return packetCollector; 779 } 780 781 @Override 782 public StanzaCollector createStanzaCollector(StanzaFilter packetFilter) { 783 StanzaCollector.Configuration configuration = StanzaCollector.newConfiguration().setStanzaFilter(packetFilter); 784 return createStanzaCollector(configuration); 785 } 786 787 @Override 788 public StanzaCollector createStanzaCollector(StanzaCollector.Configuration configuration) { 789 StanzaCollector collector = new StanzaCollector(this, configuration); 790 // Add the collector to the list of active collectors. 791 collectors.add(collector); 792 return collector; 793 } 794 795 @Override 796 public void removeStanzaCollector(StanzaCollector collector) { 797 collectors.remove(collector); 798 } 799 800 @Override 801 @Deprecated 802 public void addPacketListener(StanzaListener packetListener, StanzaFilter packetFilter) { 803 addAsyncStanzaListener(packetListener, packetFilter); 804 } 805 806 @Override 807 @Deprecated 808 public boolean removePacketListener(StanzaListener packetListener) { 809 return removeAsyncStanzaListener(packetListener); 810 } 811 812 @Override 813 public void addSyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter) { 814 if (packetListener == null) { 815 throw new NullPointerException("Packet listener is null."); 816 } 817 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 818 synchronized (syncRecvListeners) { 819 syncRecvListeners.put(packetListener, wrapper); 820 } 821 } 822 823 @Override 824 public boolean removeSyncStanzaListener(StanzaListener packetListener) { 825 synchronized (syncRecvListeners) { 826 return syncRecvListeners.remove(packetListener) != null; 827 } 828 } 829 830 @Override 831 public void addAsyncStanzaListener(StanzaListener packetListener, StanzaFilter packetFilter) { 832 if (packetListener == null) { 833 throw new NullPointerException("Packet listener is null."); 834 } 835 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 836 synchronized (asyncRecvListeners) { 837 asyncRecvListeners.put(packetListener, wrapper); 838 } 839 } 840 841 @Override 842 public boolean removeAsyncStanzaListener(StanzaListener packetListener) { 843 synchronized (asyncRecvListeners) { 844 return asyncRecvListeners.remove(packetListener) != null; 845 } 846 } 847 848 @Override 849 public void addPacketSendingListener(StanzaListener packetListener, StanzaFilter packetFilter) { 850 if (packetListener == null) { 851 throw new NullPointerException("Packet listener is null."); 852 } 853 ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); 854 synchronized (sendListeners) { 855 sendListeners.put(packetListener, wrapper); 856 } 857 } 858 859 @Override 860 public void removePacketSendingListener(StanzaListener packetListener) { 861 synchronized (sendListeners) { 862 sendListeners.remove(packetListener); 863 } 864 } 865 866 /** 867 * Process all stanza(/packet) listeners for sending packets. 868 * <p> 869 * Compared to {@link #firePacketInterceptors(Stanza)}, the listeners will be invoked in a new thread. 870 * </p> 871 * 872 * @param packet the stanza(/packet) to process. 873 */ 874 @SuppressWarnings("javadoc") 875 protected void firePacketSendingListeners(final Stanza packet) { 876 final List<StanzaListener> listenersToNotify = new LinkedList<StanzaListener>(); 877 synchronized (sendListeners) { 878 for (ListenerWrapper listenerWrapper : sendListeners.values()) { 879 if (listenerWrapper.filterMatches(packet)) { 880 listenersToNotify.add(listenerWrapper.getListener()); 881 } 882 } 883 } 884 if (listenersToNotify.isEmpty()) { 885 return; 886 } 887 // Notify in a new thread, because we can 888 asyncGo(new Runnable() { 889 @Override 890 public void run() { 891 for (StanzaListener listener : listenersToNotify) { 892 try { 893 listener.processStanza(packet); 894 } 895 catch (Exception e) { 896 LOGGER.log(Level.WARNING, "Sending listener threw exception", e); 897 continue; 898 } 899 } 900 } 901 }); 902 } 903 904 @Override 905 public void addPacketInterceptor(StanzaListener packetInterceptor, 906 StanzaFilter packetFilter) { 907 if (packetInterceptor == null) { 908 throw new NullPointerException("Packet interceptor is null."); 909 } 910 InterceptorWrapper interceptorWrapper = new InterceptorWrapper(packetInterceptor, packetFilter); 911 synchronized (interceptors) { 912 interceptors.put(packetInterceptor, interceptorWrapper); 913 } 914 } 915 916 @Override 917 public void removePacketInterceptor(StanzaListener packetInterceptor) { 918 synchronized (interceptors) { 919 interceptors.remove(packetInterceptor); 920 } 921 } 922 923 /** 924 * Process interceptors. Interceptors may modify the stanza(/packet) that is about to be sent. 925 * Since the thread that requested to send the stanza(/packet) will invoke all interceptors, it 926 * is important that interceptors perform their work as soon as possible so that the 927 * thread does not remain blocked for a long period. 928 * 929 * @param packet the stanza(/packet) that is going to be sent to the server 930 */ 931 private void firePacketInterceptors(Stanza packet) { 932 List<StanzaListener> interceptorsToInvoke = new LinkedList<StanzaListener>(); 933 synchronized (interceptors) { 934 for (InterceptorWrapper interceptorWrapper : interceptors.values()) { 935 if (interceptorWrapper.filterMatches(packet)) { 936 interceptorsToInvoke.add(interceptorWrapper.getInterceptor()); 937 } 938 } 939 } 940 for (StanzaListener interceptor : interceptorsToInvoke) { 941 try { 942 interceptor.processStanza(packet); 943 } catch (Exception e) { 944 LOGGER.log(Level.SEVERE, "Packet interceptor threw exception", e); 945 } 946 } 947 } 948 949 /** 950 * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} 951 * by setup the system property <code>smack.debuggerClass</code> to the implementation. 952 * 953 * @throws IllegalStateException if the reader or writer isn't yet initialized. 954 * @throws IllegalArgumentException if the SmackDebugger can't be loaded. 955 */ 956 protected void initDebugger() { 957 if (reader == null || writer == null) { 958 throw new NullPointerException("Reader or writer isn't initialized."); 959 } 960 // If debugging is enabled, we open a window and write out all network traffic. 961 if (config.isDebuggerEnabled()) { 962 if (debugger == null) { 963 debugger = SmackConfiguration.createDebugger(this, writer, reader); 964 } 965 966 if (debugger == null) { 967 LOGGER.severe("Debugging enabled but could not find debugger class"); 968 } else { 969 // Obtain new reader and writer from the existing debugger 970 reader = debugger.newConnectionReader(reader); 971 writer = debugger.newConnectionWriter(writer); 972 } 973 } 974 } 975 976 @SuppressWarnings("deprecation") 977 @Override 978 public long getPacketReplyTimeout() { 979 return getReplyTimeout(); 980 } 981 982 @SuppressWarnings("deprecation") 983 @Override 984 public void setPacketReplyTimeout(long timeout) { 985 setReplyTimeout(timeout); 986 } 987 988 @Override 989 public long getReplyTimeout() { 990 return replyTimeout; 991 } 992 993 @Override 994 public void setReplyTimeout(long timeout) { 995 replyTimeout = timeout; 996 } 997 998 /** 999 * Set the default value used to determine if new connection will reply to unknown IQ requests. The pre-configured 1000 * default is 'true'. 1001 * 1002 * @param replyToUnkownIqDefault 1003 * @see #setReplyToUnknownIq(boolean) 1004 * @deprecated Use {@link SmackConfiguration#setUnknownIqRequestReplyMode(org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode)} instead. 1005 */ 1006 @Deprecated 1007 // TODO Remove in Smack 4.3 1008 public static void setReplyToUnknownIqDefault(boolean replyToUnkownIqDefault) { 1009 SmackConfiguration.UnknownIqRequestReplyMode mode; 1010 if (replyToUnkownIqDefault) { 1011 mode = SmackConfiguration.UnknownIqRequestReplyMode.replyServiceUnavailable; 1012 } else { 1013 mode = SmackConfiguration.UnknownIqRequestReplyMode.doNotReply; 1014 } 1015 SmackConfiguration.setUnknownIqRequestReplyMode(mode); 1016 } 1017 1018 private SmackConfiguration.UnknownIqRequestReplyMode unknownIqRequestReplyMode = SmackConfiguration.getUnknownIqRequestReplyMode(); 1019 1020 /** 1021 * Set how Smack behaves when an unknown IQ request has been received. 1022 * 1023 * @param unknownIqRequestReplyMode reply mode. 1024 */ 1025 public void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) { 1026 this.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Mode must not be null"); 1027 } 1028 1029 /** 1030 * Set if Smack will automatically send 1031 * {@link org.jivesoftware.smack.packet.XMPPError.Condition#feature_not_implemented} when a request IQ without a 1032 * registered {@link IQRequestHandler} is received. 1033 * 1034 * @param replyToUnknownIq whether Smack should reply to unknown IQs or not. 1035 * @deprecated use {@link AbstractXMPPConnection#setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode)} instead. 1036 */ 1037 @Deprecated 1038 // TODO Remove in Smack 4.3 1039 public void setReplyToUnknownIq(boolean replyToUnknownIq) { 1040 SmackConfiguration.UnknownIqRequestReplyMode mode; 1041 if (replyToUnknownIq) { 1042 mode = SmackConfiguration.UnknownIqRequestReplyMode.replyServiceUnavailable; 1043 } else { 1044 mode = SmackConfiguration.UnknownIqRequestReplyMode.doNotReply; 1045 } 1046 unknownIqRequestReplyMode = mode; 1047 } 1048 1049 protected void parseAndProcessStanza(XmlPullParser parser) throws Exception { 1050 ParserUtils.assertAtStartTag(parser); 1051 int parserDepth = parser.getDepth(); 1052 Stanza stanza = null; 1053 try { 1054 stanza = PacketParserUtils.parseStanza(parser); 1055 } 1056 catch (Exception e) { 1057 CharSequence content = PacketParserUtils.parseContentDepth(parser, 1058 parserDepth); 1059 UnparseableStanza message = new UnparseableStanza(content, e); 1060 ParsingExceptionCallback callback = getParsingExceptionCallback(); 1061 if (callback != null) { 1062 callback.handleUnparsableStanza(message); 1063 } 1064 } 1065 ParserUtils.assertAtEndTag(parser); 1066 if (stanza != null) { 1067 processStanza(stanza); 1068 } 1069 } 1070 1071 /** 1072 * Processes a stanza(/packet) after it's been fully parsed by looping through the installed 1073 * stanza(/packet) collectors and listeners and letting them examine the stanza(/packet) to see if 1074 * they are a match with the filter. 1075 * 1076 * @param stanza the stanza to process. 1077 * @throws InterruptedException 1078 */ 1079 protected void processStanza(final Stanza stanza) throws InterruptedException { 1080 assert (stanza != null); 1081 lastStanzaReceived = System.currentTimeMillis(); 1082 // Deliver the incoming packet to listeners. 1083 executorService.executeBlocking(new Runnable() { 1084 @Override 1085 public void run() { 1086 invokeStanzaCollectorsAndNotifyRecvListeners(stanza); 1087 } 1088 }); 1089 } 1090 1091 /** 1092 * Invoke {@link StanzaCollector#processStanza(Stanza)} for every 1093 * StanzaCollector with the given packet. Also notify the receive listeners with a matching stanza(/packet) filter about the packet. 1094 * 1095 * @param packet the stanza(/packet) to notify the StanzaCollectors and receive listeners about. 1096 */ 1097 protected void invokeStanzaCollectorsAndNotifyRecvListeners(final Stanza packet) { 1098 if (packet instanceof IQ) { 1099 final IQ iq = (IQ) packet; 1100 final IQ.Type type = iq.getType(); 1101 switch (type) { 1102 case set: 1103 case get: 1104 final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace()); 1105 IQRequestHandler iqRequestHandler = null; 1106 switch (type) { 1107 case set: 1108 synchronized (setIqRequestHandler) { 1109 iqRequestHandler = setIqRequestHandler.get(key); 1110 } 1111 break; 1112 case get: 1113 synchronized (getIqRequestHandler) { 1114 iqRequestHandler = getIqRequestHandler.get(key); 1115 } 1116 break; 1117 default: 1118 throw new IllegalStateException("Should only encounter IQ type 'get' or 'set'"); 1119 } 1120 if (iqRequestHandler == null) { 1121 XMPPError.Condition replyCondition; 1122 switch (unknownIqRequestReplyMode) { 1123 case doNotReply: 1124 return; 1125 case replyFeatureNotImplemented: 1126 replyCondition = XMPPError.Condition.feature_not_implemented; 1127 break; 1128 case replyServiceUnavailable: 1129 replyCondition = XMPPError.Condition.service_unavailable; 1130 break; 1131 default: 1132 throw new AssertionError(); 1133 } 1134 1135 // If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an 1136 // IQ of type 'error' with condition 'service-unavailable'. 1137 ErrorIQ errorIQ = IQ.createErrorResponse(iq, XMPPError.getBuilder(( 1138 replyCondition))); 1139 try { 1140 sendStanza(errorIQ); 1141 } 1142 catch (InterruptedException | NotConnectedException e) { 1143 LOGGER.log(Level.WARNING, "Exception while sending error IQ to unkown IQ request", e); 1144 } 1145 } else { 1146 ExecutorService executorService = null; 1147 switch (iqRequestHandler.getMode()) { 1148 case sync: 1149 executorService = singleThreadedExecutorService; 1150 break; 1151 case async: 1152 executorService = cachedExecutorService; 1153 break; 1154 } 1155 final IQRequestHandler finalIqRequestHandler = iqRequestHandler; 1156 executorService.execute(new Runnable() { 1157 @Override 1158 public void run() { 1159 IQ response = finalIqRequestHandler.handleIQRequest(iq); 1160 if (response == null) { 1161 // It is not ideal if the IQ request handler does not return an IQ response, because RFC 1162 // 6120 § 8.1.2 does specify that a response is mandatory. But some APIs, mostly the 1163 // file transfer one, does not always return a result, so we need to handle this case. 1164 // Also sometimes a request handler may decide that it's better to not send a response, 1165 // e.g. to avoid presence leaks. 1166 return; 1167 } 1168 try { 1169 sendStanza(response); 1170 } 1171 catch (InterruptedException | NotConnectedException e) { 1172 LOGGER.log(Level.WARNING, "Exception while sending response to IQ request", e); 1173 } 1174 } 1175 }); 1176 // The following returns makes it impossible for packet listeners and collectors to 1177 // filter for IQ request stanzas, i.e. IQs of type 'set' or 'get'. This is the 1178 // desired behavior. 1179 return; 1180 } 1181 break; 1182 default: 1183 break; 1184 } 1185 } 1186 1187 // First handle the async recv listeners. Note that this code is very similar to what follows a few lines below, 1188 // the only difference is that asyncRecvListeners is used here and that the packet listeners are started in 1189 // their own thread. 1190 final Collection<StanzaListener> listenersToNotify = new LinkedList<StanzaListener>(); 1191 synchronized (asyncRecvListeners) { 1192 for (ListenerWrapper listenerWrapper : asyncRecvListeners.values()) { 1193 if (listenerWrapper.filterMatches(packet)) { 1194 listenersToNotify.add(listenerWrapper.getListener()); 1195 } 1196 } 1197 } 1198 1199 for (final StanzaListener listener : listenersToNotify) { 1200 asyncGo(new Runnable() { 1201 @Override 1202 public void run() { 1203 try { 1204 listener.processStanza(packet); 1205 } catch (Exception e) { 1206 LOGGER.log(Level.SEVERE, "Exception in async packet listener", e); 1207 } 1208 } 1209 }); 1210 } 1211 1212 // Loop through all collectors and notify the appropriate ones. 1213 for (StanzaCollector collector : collectors) { 1214 collector.processStanza(packet); 1215 } 1216 1217 // Notify the receive listeners interested in the packet 1218 listenersToNotify.clear(); 1219 synchronized (syncRecvListeners) { 1220 for (ListenerWrapper listenerWrapper : syncRecvListeners.values()) { 1221 if (listenerWrapper.filterMatches(packet)) { 1222 listenersToNotify.add(listenerWrapper.getListener()); 1223 } 1224 } 1225 } 1226 1227 // Decouple incoming stanza processing from listener invocation. Unlike async listeners, this uses a single 1228 // threaded executor service and therefore keeps the order. 1229 singleThreadedExecutorService.execute(new Runnable() { 1230 @Override 1231 public void run() { 1232 for (StanzaListener listener : listenersToNotify) { 1233 try { 1234 listener.processStanza(packet); 1235 } catch (NotConnectedException e) { 1236 LOGGER.log(Level.WARNING, "Got not connected exception, aborting", e); 1237 break; 1238 } catch (Exception e) { 1239 LOGGER.log(Level.SEVERE, "Exception in packet listener", e); 1240 } 1241 } 1242 } 1243 }); 1244 1245 } 1246 1247 /** 1248 * Sets whether the connection has already logged in the server. This method assures that the 1249 * {@link #wasAuthenticated} flag is never reset once it has ever been set. 1250 * 1251 */ 1252 protected void setWasAuthenticated() { 1253 // Never reset the flag if the connection has ever been authenticated 1254 if (!wasAuthenticated) { 1255 wasAuthenticated = authenticated; 1256 } 1257 } 1258 1259 protected void callConnectionConnectedListener() { 1260 for (ConnectionListener listener : connectionListeners) { 1261 listener.connected(this); 1262 } 1263 } 1264 1265 protected void callConnectionAuthenticatedListener(boolean resumed) { 1266 for (ConnectionListener listener : connectionListeners) { 1267 try { 1268 listener.authenticated(this, resumed); 1269 } catch (Exception e) { 1270 // Catch and print any exception so we can recover 1271 // from a faulty listener and finish the shutdown process 1272 LOGGER.log(Level.SEVERE, "Exception in authenticated listener", e); 1273 } 1274 } 1275 } 1276 1277 void callConnectionClosedListener() { 1278 for (ConnectionListener listener : connectionListeners) { 1279 try { 1280 listener.connectionClosed(); 1281 } 1282 catch (Exception e) { 1283 // Catch and print any exception so we can recover 1284 // from a faulty listener and finish the shutdown process 1285 LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e); 1286 } 1287 } 1288 } 1289 1290 protected void callConnectionClosedOnErrorListener(Exception e) { 1291 boolean logWarning = true; 1292 if (e instanceof StreamErrorException) { 1293 StreamErrorException see = (StreamErrorException) e; 1294 if (see.getStreamError().getCondition() == StreamError.Condition.not_authorized 1295 && wasAuthenticated) { 1296 logWarning = false; 1297 LOGGER.log(Level.FINE, 1298 "Connection closed with not-authorized stream error after it was already authenticated. The account was likely deleted/unregistered on the server"); 1299 } 1300 } 1301 if (logWarning) { 1302 LOGGER.log(Level.WARNING, "Connection " + this + " closed with error", e); 1303 } 1304 for (ConnectionListener listener : connectionListeners) { 1305 try { 1306 listener.connectionClosedOnError(e); 1307 } 1308 catch (Exception e2) { 1309 // Catch and print any exception so we can recover 1310 // from a faulty listener 1311 LOGGER.log(Level.SEVERE, "Error in listener while closing connection", e2); 1312 } 1313 } 1314 } 1315 1316 /** 1317 * Sends a notification indicating that the connection was reconnected successfully. 1318 */ 1319 // TODO: Remove in Smack 4.3 1320 @Deprecated 1321 protected void notifyReconnection() { 1322 // Notify connection listeners of the reconnection. 1323 for (ConnectionListener listener : connectionListeners) { 1324 try { 1325 listener.reconnectionSuccessful(); 1326 } 1327 catch (Exception e) { 1328 // Catch and print any exception so we can recover 1329 // from a faulty listener 1330 LOGGER.log(Level.WARNING, "notifyReconnection()", e); 1331 } 1332 } 1333 } 1334 1335 /** 1336 * A wrapper class to associate a stanza(/packet) filter with a listener. 1337 */ 1338 protected static class ListenerWrapper { 1339 1340 private final StanzaListener packetListener; 1341 private final StanzaFilter packetFilter; 1342 1343 /** 1344 * Create a class which associates a stanza(/packet) filter with a listener. 1345 * 1346 * @param packetListener the stanza(/packet) listener. 1347 * @param packetFilter the associated filter or null if it listen for all packets. 1348 */ 1349 public ListenerWrapper(StanzaListener packetListener, StanzaFilter packetFilter) { 1350 this.packetListener = packetListener; 1351 this.packetFilter = packetFilter; 1352 } 1353 1354 public boolean filterMatches(Stanza packet) { 1355 return packetFilter == null || packetFilter.accept(packet); 1356 } 1357 1358 public StanzaListener getListener() { 1359 return packetListener; 1360 } 1361 } 1362 1363 /** 1364 * A wrapper class to associate a stanza(/packet) filter with an interceptor. 1365 */ 1366 protected static class InterceptorWrapper { 1367 1368 private final StanzaListener packetInterceptor; 1369 private final StanzaFilter packetFilter; 1370 1371 /** 1372 * Create a class which associates a stanza(/packet) filter with an interceptor. 1373 * 1374 * @param packetInterceptor the interceptor. 1375 * @param packetFilter the associated filter or null if it intercepts all packets. 1376 */ 1377 public InterceptorWrapper(StanzaListener packetInterceptor, StanzaFilter packetFilter) { 1378 this.packetInterceptor = packetInterceptor; 1379 this.packetFilter = packetFilter; 1380 } 1381 1382 public boolean filterMatches(Stanza packet) { 1383 return packetFilter == null || packetFilter.accept(packet); 1384 } 1385 1386 public StanzaListener getInterceptor() { 1387 return packetInterceptor; 1388 } 1389 } 1390 1391 @Override 1392 public int getConnectionCounter() { 1393 return connectionCounterValue; 1394 } 1395 1396 @Override 1397 public void setFromMode(FromMode fromMode) { 1398 this.fromMode = fromMode; 1399 } 1400 1401 @Override 1402 public FromMode getFromMode() { 1403 return this.fromMode; 1404 } 1405 1406 @Override 1407 protected void finalize() throws Throwable { 1408 LOGGER.fine("finalizing " + this + ": Shutting down executor services"); 1409 try { 1410 // It's usually not a good idea to rely on finalize. But this is the easiest way to 1411 // avoid the "Smack Listener Processor" leaking. The thread(s) of the executor have a 1412 // reference to their ExecutorService which prevents the ExecutorService from being 1413 // gc'ed. It is possible that the XMPPConnection instance is gc'ed while the 1414 // listenerExecutor ExecutorService call not be gc'ed until it got shut down. 1415 executorService.shutdownNow(); 1416 cachedExecutorService.shutdown(); 1417 removeCallbacksService.shutdownNow(); 1418 singleThreadedExecutorService.shutdownNow(); 1419 } catch (Throwable t) { 1420 LOGGER.log(Level.WARNING, "finalize() threw trhowable", t); 1421 } 1422 finally { 1423 super.finalize(); 1424 } 1425 } 1426 1427 protected final void parseFeatures(XmlPullParser parser) throws Exception { 1428 streamFeatures.clear(); 1429 final int initialDepth = parser.getDepth(); 1430 while (true) { 1431 int eventType = parser.next(); 1432 1433 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) { 1434 ExtensionElement streamFeature = null; 1435 String name = parser.getName(); 1436 String namespace = parser.getNamespace(); 1437 switch (name) { 1438 case StartTls.ELEMENT: 1439 streamFeature = PacketParserUtils.parseStartTlsFeature(parser); 1440 break; 1441 case Mechanisms.ELEMENT: 1442 streamFeature = new Mechanisms(PacketParserUtils.parseMechanisms(parser)); 1443 break; 1444 case Bind.ELEMENT: 1445 streamFeature = Bind.Feature.INSTANCE; 1446 break; 1447 case Session.ELEMENT: 1448 streamFeature = PacketParserUtils.parseSessionFeature(parser); 1449 break; 1450 case Compress.Feature.ELEMENT: 1451 streamFeature = PacketParserUtils.parseCompressionFeature(parser); 1452 break; 1453 default: 1454 ExtensionElementProvider<ExtensionElement> provider = ProviderManager.getStreamFeatureProvider(name, namespace); 1455 if (provider != null) { 1456 streamFeature = provider.parse(parser); 1457 } 1458 break; 1459 } 1460 if (streamFeature != null) { 1461 addStreamFeature(streamFeature); 1462 } 1463 } 1464 else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initialDepth) { 1465 break; 1466 } 1467 } 1468 1469 if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) { 1470 // Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it 1471 if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE) 1472 || config.getSecurityMode() == SecurityMode.disabled) { 1473 tlsHandled.reportSuccess(); 1474 saslFeatureReceived.reportSuccess(); 1475 } 1476 } 1477 1478 // If the server reported the bind feature then we are that that we did SASL and maybe 1479 // STARTTLS. We can then report that the last 'stream:features' have been parsed 1480 if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) { 1481 if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE) 1482 || !config.isCompressionEnabled()) { 1483 // This was was last features from the server is either it did not contain 1484 // compression or if we disabled it 1485 lastFeaturesReceived.reportSuccess(); 1486 } 1487 } 1488 afterFeaturesReceived(); 1489 } 1490 1491 @SuppressWarnings("unused") 1492 protected void afterFeaturesReceived() throws SecurityRequiredException, NotConnectedException, InterruptedException { 1493 // Default implementation does nothing 1494 } 1495 1496 @SuppressWarnings("unchecked") 1497 @Override 1498 public <F extends ExtensionElement> F getFeature(String element, String namespace) { 1499 return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace)); 1500 } 1501 1502 @Override 1503 public boolean hasFeature(String element, String namespace) { 1504 return getFeature(element, namespace) != null; 1505 } 1506 1507 protected void addStreamFeature(ExtensionElement feature) { 1508 String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace()); 1509 streamFeatures.put(key, feature); 1510 } 1511 1512 @Override 1513 public void sendStanzaWithResponseCallback(Stanza stanza, StanzaFilter replyFilter, 1514 StanzaListener callback) throws NotConnectedException, InterruptedException { 1515 sendStanzaWithResponseCallback(stanza, replyFilter, callback, null); 1516 } 1517 1518 @Override 1519 public void sendStanzaWithResponseCallback(Stanza stanza, StanzaFilter replyFilter, 1520 StanzaListener callback, ExceptionCallback exceptionCallback) 1521 throws NotConnectedException, InterruptedException { 1522 sendStanzaWithResponseCallback(stanza, replyFilter, callback, exceptionCallback, 1523 getReplyTimeout()); 1524 } 1525 1526 @Override 1527 public void sendStanzaWithResponseCallback(Stanza stanza, final StanzaFilter replyFilter, 1528 final StanzaListener callback, final ExceptionCallback exceptionCallback, 1529 long timeout) throws NotConnectedException, InterruptedException { 1530 Objects.requireNonNull(stanza, "stanza must not be null"); 1531 // While Smack allows to add PacketListeners with a PacketFilter value of 'null', we 1532 // disallow it here in the async API as it makes no sense 1533 Objects.requireNonNull(replyFilter, "replyFilter must not be null"); 1534 Objects.requireNonNull(callback, "callback must not be null"); 1535 1536 final StanzaListener packetListener = new StanzaListener() { 1537 @Override 1538 public void processStanza(Stanza packet) throws NotConnectedException, InterruptedException, NotLoggedInException { 1539 boolean removed = removeAsyncStanzaListener(this); 1540 if (!removed) { 1541 // We lost a race against the "no response" handling runnable. Avoid calling the callback, as the 1542 // exception callback will be invoked (if any). 1543 return; 1544 } 1545 try { 1546 XMPPErrorException.ifHasErrorThenThrow(packet); 1547 callback.processStanza(packet); 1548 } 1549 catch (XMPPErrorException e) { 1550 if (exceptionCallback != null) { 1551 exceptionCallback.processException(e); 1552 } 1553 } 1554 } 1555 }; 1556 removeCallbacksService.schedule(new Runnable() { 1557 @Override 1558 public void run() { 1559 boolean removed = removeAsyncStanzaListener(packetListener); 1560 // If the packetListener got removed, then it was never run and 1561 // we never received a response, inform the exception callback 1562 if (removed && exceptionCallback != null) { 1563 Exception exception; 1564 if (!isConnected()) { 1565 // If the connection is no longer connected, throw a not connected exception. 1566 exception = new NotConnectedException(AbstractXMPPConnection.this, replyFilter); 1567 } else { 1568 exception = NoResponseException.newWith(AbstractXMPPConnection.this, replyFilter); 1569 } 1570 exceptionCallback.processException(exception); 1571 } 1572 } 1573 }, timeout, TimeUnit.MILLISECONDS); 1574 addAsyncStanzaListener(packetListener, replyFilter); 1575 sendStanza(stanza); 1576 } 1577 1578 @Override 1579 public void sendIqWithResponseCallback(IQ iqRequest, StanzaListener callback) 1580 throws NotConnectedException, InterruptedException { 1581 sendIqWithResponseCallback(iqRequest, callback, null); 1582 } 1583 1584 @Override 1585 public void sendIqWithResponseCallback(IQ iqRequest, StanzaListener callback, 1586 ExceptionCallback exceptionCallback) throws NotConnectedException, InterruptedException { 1587 sendIqWithResponseCallback(iqRequest, callback, exceptionCallback, getReplyTimeout()); 1588 } 1589 1590 @Override 1591 public void sendIqWithResponseCallback(IQ iqRequest, final StanzaListener callback, 1592 final ExceptionCallback exceptionCallback, long timeout) 1593 throws NotConnectedException, InterruptedException { 1594 StanzaFilter replyFilter = new IQReplyFilter(iqRequest, this); 1595 sendStanzaWithResponseCallback(iqRequest, replyFilter, callback, exceptionCallback, timeout); 1596 } 1597 1598 @Override 1599 public void addOneTimeSyncCallback(final StanzaListener callback, final StanzaFilter packetFilter) { 1600 final StanzaListener packetListener = new StanzaListener() { 1601 @Override 1602 public void processStanza(Stanza packet) throws NotConnectedException, InterruptedException, NotLoggedInException { 1603 try { 1604 callback.processStanza(packet); 1605 } finally { 1606 removeSyncStanzaListener(this); 1607 } 1608 } 1609 }; 1610 addSyncStanzaListener(packetListener, packetFilter); 1611 removeCallbacksService.schedule(new Runnable() { 1612 @Override 1613 public void run() { 1614 removeSyncStanzaListener(packetListener); 1615 } 1616 }, getReplyTimeout(), TimeUnit.MILLISECONDS); 1617 } 1618 1619 @Override 1620 public IQRequestHandler registerIQRequestHandler(final IQRequestHandler iqRequestHandler) { 1621 final String key = XmppStringUtils.generateKey(iqRequestHandler.getElement(), iqRequestHandler.getNamespace()); 1622 switch (iqRequestHandler.getType()) { 1623 case set: 1624 synchronized (setIqRequestHandler) { 1625 return setIqRequestHandler.put(key, iqRequestHandler); 1626 } 1627 case get: 1628 synchronized (getIqRequestHandler) { 1629 return getIqRequestHandler.put(key, iqRequestHandler); 1630 } 1631 default: 1632 throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed"); 1633 } 1634 } 1635 1636 @Override 1637 public final IQRequestHandler unregisterIQRequestHandler(IQRequestHandler iqRequestHandler) { 1638 return unregisterIQRequestHandler(iqRequestHandler.getElement(), iqRequestHandler.getNamespace(), 1639 iqRequestHandler.getType()); 1640 } 1641 1642 @Override 1643 public IQRequestHandler unregisterIQRequestHandler(String element, String namespace, IQ.Type type) { 1644 final String key = XmppStringUtils.generateKey(element, namespace); 1645 switch (type) { 1646 case set: 1647 synchronized (setIqRequestHandler) { 1648 return setIqRequestHandler.remove(key); 1649 } 1650 case get: 1651 synchronized (getIqRequestHandler) { 1652 return getIqRequestHandler.remove(key); 1653 } 1654 default: 1655 throw new IllegalArgumentException("Only IQ type of 'get' and 'set' allowed"); 1656 } 1657 } 1658 1659 private long lastStanzaReceived; 1660 1661 @Override 1662 public long getLastStanzaReceived() { 1663 return lastStanzaReceived; 1664 } 1665 1666 /** 1667 * Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a 1668 * stanza. 1669 * 1670 * @param callback the callback to install 1671 */ 1672 public void setParsingExceptionCallback(ParsingExceptionCallback callback) { 1673 parsingExceptionCallback = callback; 1674 } 1675 1676 /** 1677 * Get the current active parsing exception callback. 1678 * 1679 * @return the active exception callback or null if there is none 1680 */ 1681 public ParsingExceptionCallback getParsingExceptionCallback() { 1682 return parsingExceptionCallback; 1683 } 1684 1685 @Override 1686 public final String toString() { 1687 EntityFullJid localEndpoint = getUser(); 1688 String localEndpointString = (localEndpoint == null ? "not-authenticated" : localEndpoint.toString()); 1689 return getClass().getSimpleName() + '[' + localEndpointString + "] (" + getConnectionCounter() + ')'; 1690 } 1691 1692 protected final void asyncGo(Runnable runnable) { 1693 cachedExecutorService.execute(runnable); 1694 } 1695 1696 protected final ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { 1697 return removeCallbacksService.schedule(runnable, delay, unit); 1698 } 1699}