001/**
002 *
003 * Copyright 2014-2020 Florian Schmaus
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.security.cert.CertificateException;
020import java.util.Collections;
021import java.util.List;
022
023import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsFailed;
024import org.jivesoftware.smack.filter.StanzaFilter;
025import org.jivesoftware.smack.util.StringUtils;
026import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
027import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
028import org.jivesoftware.smack.util.rce.RemoteConnectionException;
029
030import org.jxmpp.jid.Jid;
031
032/**
033 * Smack uses SmackExceptions for errors that are not defined by any XMPP specification.
034 *
035 * @author Florian Schmaus
036 */
037public abstract class SmackException extends Exception {
038
039    /**
040     *
041     */
042    private static final long serialVersionUID = 1844674365368214458L;
043
044    /**
045     * Creates a new SmackException with the Throwable that was the root cause of the exception.
046     *
047     * @param wrappedThrowable the root cause of the exception.
048     */
049    protected SmackException(Throwable wrappedThrowable) {
050        super(wrappedThrowable);
051    }
052
053    protected SmackException(String message) {
054        super(message);
055    }
056
057    protected SmackException(String message, Throwable wrappedThrowable) {
058        super(message, wrappedThrowable);
059    }
060
061    protected SmackException() {
062    }
063
064    /**
065     * Exception thrown always when there was no response to an request within the stanza reply timeout of the used
066     * connection instance. You can modify (e.g. increase) the stanza reply timeout with
067     * {@link XMPPConnection#setReplyTimeout(long)}.
068     */
069    public static final class NoResponseException extends SmackException {
070        /**
071         *
072         */
073        private static final long serialVersionUID = -6523363748984543636L;
074
075        private final StanzaFilter filter;
076
077        private NoResponseException(String message) {
078            this(message, null);
079        }
080
081        private NoResponseException(String message, StanzaFilter filter) {
082            super(message);
083            this.filter = filter;
084        }
085
086        /**
087         * Get the filter that was used to collect the response.
088         *
089         * @return the used filter or <code>null</code>.
090         */
091        public StanzaFilter getFilter() {
092            return filter;
093        }
094
095        public static NoResponseException newWith(XMPPConnection connection, String waitingFor) {
096            final StringBuilder sb = getWaitingFor(connection);
097            sb.append(" While waiting for ").append(waitingFor);
098            sb.append(" [").append(connection).append(']');
099            return new NoResponseException(sb.toString());
100        }
101
102        public static NoResponseException newWith(long timeout,
103                        StanzaCollector collector, boolean stanzaCollectorCancelled) {
104            return newWith(timeout, collector.getStanzaFilter(), stanzaCollectorCancelled);
105        }
106
107        public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) {
108            return newWith(connection.getReplyTimeout(), filter, false);
109        }
110
111        public static NoResponseException newWith(long timeout, StanzaFilter filter, boolean stanzaCollectorCancelled) {
112            final StringBuilder sb = getWaitingFor(timeout);
113            if (stanzaCollectorCancelled) {
114                sb.append(" StanzaCollector has been cancelled.");
115            }
116            sb.append(" Waited for response using: ");
117            if (filter != null) {
118                sb.append(filter.toString());
119            }
120            else {
121                sb.append("No filter used or filter was 'null'");
122            }
123            sb.append('.');
124            return new NoResponseException(sb.toString(), filter);
125        }
126
127        private static StringBuilder getWaitingFor(XMPPConnection connection) {
128            return getWaitingFor(connection.getReplyTimeout());
129        }
130
131        private static StringBuilder getWaitingFor(final long replyTimeout) {
132            final StringBuilder sb = new StringBuilder(256);
133            sb.append("No response received within reply timeout. Timeout was "
134                            + replyTimeout + "ms (~"
135                            + replyTimeout / 1000 + "s).");
136            return sb;
137        }
138    }
139
140    public static class NotLoggedInException extends SmackException {
141
142        /**
143         *
144         */
145        private static final long serialVersionUID = 3216216839100019278L;
146
147        public NotLoggedInException() {
148            super("Client is not logged in");
149        }
150    }
151
152    public static class AlreadyLoggedInException extends SmackException {
153
154        /**
155         *
156         */
157        private static final long serialVersionUID = 5011416918049935231L;
158
159        public AlreadyLoggedInException() {
160            super("Client is already logged in");
161        }
162    }
163
164    public static class AlreadyConnectedException extends SmackException {
165
166        /**
167         *
168         */
169        private static final long serialVersionUID = 5011416918049135231L;
170
171        public AlreadyConnectedException() {
172            super("Client is already connected");
173        }
174    }
175
176    public static class NotConnectedException extends SmackException {
177
178        /**
179         *
180         */
181        private static final long serialVersionUID = 9197980400776001173L;
182
183        public NotConnectedException() {
184            this(null);
185        }
186
187        public NotConnectedException(String optionalHint) {
188            super("Client is not, or no longer, connected."
189                            + (optionalHint != null ? ' ' + optionalHint : ""));
190        }
191
192        public NotConnectedException(XMPPConnection connection, String details) {
193            super("The connection " + connection.toString() + " is no longer connected. "
194                            + details);
195        }
196
197        public NotConnectedException(XMPPConnection connection, StanzaFilter stanzaFilter) {
198            super("The connection " + connection
199                            + " is no longer connected while waiting for response with " + stanzaFilter);
200        }
201
202        public NotConnectedException(XMPPConnection connection, StanzaFilter stanzaFilter,
203                        Exception connectionException) {
204            super("The connection " + connection + " is no longer connected while waiting for response with "
205                            + stanzaFilter + " because of " + connectionException, connectionException);
206        }
207    }
208
209    public static class OutgoingQueueFullException extends SmackException {
210
211        private static final long serialVersionUID = 1L;
212
213    }
214
215    public static class IllegalStateChangeException extends SmackException {
216
217        /**
218         *
219         */
220        private static final long serialVersionUID = -1766023961577168927L;
221
222        public IllegalStateChangeException() {
223        }
224    }
225
226    public abstract static class SecurityRequiredException extends SmackException {
227
228        /**
229         *
230         */
231        private static final long serialVersionUID = 384291845029773545L;
232
233        public SecurityRequiredException(String message) {
234            super(message);
235        }
236    }
237
238    public static class SecurityRequiredByClientException extends SecurityRequiredException {
239        /**
240         *
241         */
242        private static final long serialVersionUID = 2395325821201543159L;
243
244        public SecurityRequiredByClientException() {
245            super("SSL/TLS required by client but not supported by server");
246        }
247    }
248
249    public static class SecurityRequiredByServerException extends SecurityRequiredException {
250        /**
251         *
252         */
253        private static final long serialVersionUID = 8268148813117631819L;
254
255        public SecurityRequiredByServerException() {
256            super("SSL/TLS required by server but disabled in client");
257        }
258    }
259
260    public static class SecurityNotPossibleException extends SmackException {
261
262        /**
263         *
264         */
265        private static final long serialVersionUID = -6836090872690331336L;
266
267        public SecurityNotPossibleException(String message) {
268            super(message);
269        }
270    }
271
272    public abstract static class ConnectionException extends SmackException {
273
274        private static final long serialVersionUID = 1L;
275
276        protected ConnectionException(Throwable wrappedThrowable) {
277            super(wrappedThrowable);
278        }
279
280        protected ConnectionException(String message) {
281            super(message);
282        }
283
284    }
285
286    public static final class GenericConnectionException extends ConnectionException {
287
288        private static final long serialVersionUID = 1L;
289
290        /**
291         * Deprecated, do not use.
292         *
293         * @param wrappedThrowable the wrapped throwable.
294         */
295        @Deprecated
296        public GenericConnectionException(Throwable wrappedThrowable) {
297            super(wrappedThrowable);
298        }
299    }
300
301    /**
302     * This exception is thrown if Smack is unable to connect to all hosts of a given XMPP
303     * service. The connection exceptions can be retrieved with
304     * {@link EndpointConnectionException#getConnectionExceptions()}, which will have the exception causing the
305     * connection failure set and retrievable with {@link RemoteConnectionException#getException()}.
306     */
307    public static final class EndpointConnectionException extends ConnectionException {
308
309        /**
310         *
311         */
312        private static final long serialVersionUID = 1;
313
314        private final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
315        private final List<? extends RemoteConnectionException<?>> connectionExceptions;
316
317        private EndpointConnectionException(String message, List<RemoteConnectionEndpointLookupFailure> lookupFailures,
318                        List<? extends RemoteConnectionException<?>> connectionExceptions) {
319            super(message);
320            // At least one list must contain an entry.
321            assert !lookupFailures.isEmpty() || !connectionExceptions.isEmpty();
322            this.lookupFailures = lookupFailures;
323            this.connectionExceptions = connectionExceptions;
324        }
325
326        public static EndpointConnectionException from(List<RemoteConnectionEndpointLookupFailure> lookupFailures,
327                        List<? extends RemoteConnectionException<?>> connectionExceptions) {
328            StringBuilder sb = new StringBuilder(256);
329
330            if (!lookupFailures.isEmpty()) {
331                sb.append("Could not lookup the following endpoints: ");
332                StringUtils.appendTo(lookupFailures, sb);
333            }
334
335            if (!connectionExceptions.isEmpty()) {
336                sb.append("The following addresses failed: ");
337                StringUtils.appendTo(connectionExceptions, sb, rce -> sb.append(rce.getErrorMessage()));
338            }
339
340            return new EndpointConnectionException(sb.toString(), lookupFailures, connectionExceptions);
341        }
342
343        public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
344            return lookupFailures;
345        }
346
347        public List<? extends RemoteConnectionException<? extends RemoteConnectionEndpoint>> getConnectionExceptions() {
348            return connectionExceptions;
349        }
350    }
351
352    public static final class NoEndpointsDiscoveredException extends ConnectionException {
353
354        private static final long serialVersionUID = 1L;
355
356        private final List<LookupConnectionEndpointsFailed> lookupFailures;
357
358        private NoEndpointsDiscoveredException(String message, List<LookupConnectionEndpointsFailed> lookupFailures) {
359            super(message);
360            this.lookupFailures = Collections.unmodifiableList(lookupFailures);
361        }
362
363        public List<LookupConnectionEndpointsFailed> getLookupFailures() {
364            return lookupFailures;
365        }
366
367        public static NoEndpointsDiscoveredException from(List<LookupConnectionEndpointsFailed> lookupFailures) {
368            StringBuilder sb = new StringBuilder();
369
370            if (lookupFailures.isEmpty()) {
371                sb.append("No endpoint lookup finished within the timeout");
372            } else {
373                sb.append("No endpoints could be discovered due the following lookup failures: ");
374                StringUtils.appendTo(lookupFailures, sb);
375            }
376
377            return new NoEndpointsDiscoveredException(sb.toString(), lookupFailures);
378        }
379    }
380
381    public static class FeatureNotSupportedException extends SmackException {
382
383        /**
384         *
385         */
386        private static final long serialVersionUID = 4713404802621452016L;
387
388        private final String feature;
389        private final Jid jid;
390
391        public FeatureNotSupportedException(String feature) {
392            this(feature, null);
393        }
394
395        public FeatureNotSupportedException(String feature, Jid jid) {
396            super(feature + " not supported" + (jid == null ? "" : " by '" + jid + "'"));
397            this.jid = jid;
398            this.feature = feature;
399        }
400
401        /**
402         * Get the feature which is not supported.
403         *
404         * @return the feature which is not supported
405         */
406        public String getFeature() {
407            return feature;
408        }
409
410        /**
411         * Get JID which does not support the feature. The JID can be null in cases when there are
412         * multiple JIDs queried for this feature.
413         *
414         * @return the JID which does not support the feature, or null
415         */
416        public Jid getJid() {
417            return jid;
418        }
419    }
420
421    public static class ResourceBindingNotOfferedException extends SmackException {
422
423        /**
424         *
425         */
426        private static final long serialVersionUID = 2346934138253437571L;
427
428        public ResourceBindingNotOfferedException() {
429            super("Resource binding was not offered by server");
430        }
431    }
432
433    /**
434     * A Smack exception wrapping another exception. Note that usage of this class is consider bad practice. This class
435     * will eventually be marked deprecated and removed.
436     */
437    public static class SmackWrappedException extends SmackException {
438
439        /**
440         *
441         */
442        private static final long serialVersionUID = 1L;
443
444        public SmackWrappedException(Exception exception) {
445            super(exception);
446        }
447
448        public SmackWrappedException(String message, Exception exception) {
449            super(message, exception);
450        }
451    }
452
453    /**
454     * A Smack exception wrapping a text message. Note that usage of this class is consider bad practice. This class
455     * will eventually be marked deprecated and removed.
456     */
457    public static class SmackMessageException extends SmackException {
458
459        /**
460         *
461         */
462        private static final long serialVersionUID = 1L;
463
464        public SmackMessageException(String message) {
465            super(message);
466        }
467    }
468
469    public static class SmackSaslException extends SmackException {
470
471        /**
472         *
473         */
474        private static final long serialVersionUID = 1L;
475
476        public SmackSaslException(Exception exception) {
477            super(exception);
478        }
479
480        public SmackSaslException(String message) {
481            super(message);
482        }
483
484        public SmackSaslException(String message, Exception exception) {
485            super(message, exception);
486        }
487    }
488
489    public static class SmackCertificateException extends SmackException {
490
491        private static final long serialVersionUID = 1L;
492
493        private final CertificateException certificateException;
494
495        public SmackCertificateException(CertificateException certificateException) {
496            this.certificateException = certificateException;
497        }
498
499        public CertificateException getCertificateException() {
500            return certificateException;
501        }
502    }
503}