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