1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2007 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smack.packet;
22
23import java.util.*;
24
25/**
26 * Represents a XMPP error sub-packet. Typically, a server responds to a request that has
27 * problems by sending the packet back and including an error packet. Each error has a code, type,
28 * error condition as well as as an optional text explanation. Typical errors are:<p>
29 *
30 * <table border=1>
31 *      <hr><td><b>Code</b></td><td><b>XMPP Error</b></td><td><b>Type</b></td></hr>
32 *      <tr><td>500</td><td>interna-server-error</td><td>WAIT</td></tr>
33 *      <tr><td>403</td><td>forbidden</td><td>AUTH</td></tr>
34 *      <tr><td>400</td<td>bad-request</td><td>MODIFY</td>></tr>
35 *      <tr><td>404</td><td>item-not-found</td><td>CANCEL</td></tr>
36 *      <tr><td>409</td><td>conflict</td><td>CANCEL</td></tr>
37 *      <tr><td>501</td><td>feature-not-implemented</td><td>CANCEL</td></tr>
38 *      <tr><td>302</td><td>gone</td><td>MODIFY</td></tr>
39 *      <tr><td>400</td><td>jid-malformed</td><td>MODIFY</td></tr>
40 *      <tr><td>406</td><td>no-acceptable</td><td> MODIFY</td></tr>
41 *      <tr><td>405</td><td>not-allowed</td><td>CANCEL</td></tr>
42 *      <tr><td>401</td><td>not-authorized</td><td>AUTH</td></tr>
43 *      <tr><td>402</td><td>payment-required</td><td>AUTH</td></tr>
44 *      <tr><td>404</td><td>recipient-unavailable</td><td>WAIT</td></tr>
45 *      <tr><td>302</td><td>redirect</td><td>MODIFY</td></tr>
46 *      <tr><td>407</td><td>registration-required</td><td>AUTH</td></tr>
47 *      <tr><td>404</td><td>remote-server-not-found</td><td>CANCEL</td></tr>
48 *      <tr><td>504</td><td>remote-server-timeout</td><td>WAIT</td></tr>
49 *      <tr><td>502</td><td>remote-server-error</td><td>CANCEL</td></tr>
50 *      <tr><td>500</td><td>resource-constraint</td><td>WAIT</td></tr>
51 *      <tr><td>503</td><td>service-unavailable</td><td>CANCEL</td></tr>
52 *      <tr><td>407</td><td>subscription-required</td><td>AUTH</td></tr>
53 *      <tr><td>500</td><td>undefined-condition</td><td>WAIT</td></tr>
54 *      <tr><td>400</td><td>unexpected-condition</td><td>WAIT</td></tr>
55 *      <tr><td>408</td><td>request-timeout</td><td>CANCEL</td></tr>
56 * </table>
57 *
58 * @author Matt Tucker
59 */
60public class XMPPError {
61
62    private int code;
63    private Type type;
64    private String condition;
65    private String message;
66    private List<PacketExtension> applicationExtensions = null;
67
68
69    /**
70     * Creates a new error with the specified condition infering the type and code.
71     * If the Condition is predefined, client code should be like:
72     *     new XMPPError(XMPPError.Condition.remote_server_timeout);
73     * If the Condition is not predefined, invocations should be like
74     *     new XMPPError(new XMPPError.Condition("my_own_error"));
75     *
76     * @param condition the error condition.
77     */
78    public XMPPError(Condition condition) {
79        this.init(condition);
80        this.message = null;
81    }
82
83    /**
84     * Creates a new error with the specified condition and message infering the type and code.
85     * If the Condition is predefined, client code should be like:
86     *     new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation");
87     * If the Condition is not predefined, invocations should be like
88     *     new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation");
89     *
90     * @param condition the error condition.
91     * @param messageText a message describing the error.
92     */
93    public XMPPError(Condition condition, String messageText) {
94        this.init(condition);
95        this.message = messageText;
96    }
97
98    /**
99     * Creates a new  error with the specified code and no message.
100     *
101     * @param code the error code.
102     * @deprecated new errors should be created using the constructor XMPPError(condition)
103     */
104    public XMPPError(int code) {
105        this.code = code;
106        this.message = null;
107    }
108
109    /**
110     * Creates a new error with the specified code and message.
111     * deprecated
112     *
113     * @param code the error code.
114     * @param message a message describing the error.
115     * @deprecated new errors should be created using the constructor XMPPError(condition, message)
116     */
117    public XMPPError(int code, String message) {
118        this.code = code;
119        this.message = message;
120    }
121
122    /**
123     * Creates a new error with the specified code, type, condition and message.
124     * This constructor is used when the condition is not recognized automatically by XMPPError
125     * i.e. there is not a defined instance of ErrorCondition or it does not applies the default
126     * specification.
127     *
128     * @param code the error code.
129     * @param type the error type.
130     * @param condition the error condition.
131     * @param message a message describing the error.
132     * @param extension list of packet extensions
133     */
134    public XMPPError(int code, Type type, String condition, String message,
135            List<PacketExtension> extension) {
136        this.code = code;
137        this.type = type;
138        this.condition = condition;
139        this.message = message;
140        this.applicationExtensions = extension;
141    }
142
143    /**
144     * Initialize the error infering the type and code for the received condition.
145     *
146     * @param condition the error condition.
147     */
148    private void init(Condition condition) {
149        // Look for the condition and its default code and type
150        ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition);
151        this.condition = condition.value;
152        if (defaultErrorSpecification != null) {
153            // If there is a default error specification for the received condition,
154            // it get configured with the infered type and code.
155            this.type = defaultErrorSpecification.getType();
156            this.code = defaultErrorSpecification.getCode();
157        }
158    }
159    /**
160     * Returns the error condition.
161     *
162     * @return the error condition.
163     */
164    public String getCondition() {
165        return condition;
166    }
167
168    /**
169     * Returns the error type.
170     *
171     * @return the error type.
172     */
173    public Type getType() {
174        return type;
175    }
176
177    /**
178     * Returns the error code.
179     *
180     * @return the error code.
181     */
182    public int getCode() {
183        return code;
184    }
185
186    /**
187     * Returns the message describing the error, or null if there is no message.
188     *
189     * @return the message describing the error, or null if there is no message.
190     */
191    public String getMessage() {
192        return message;
193    }
194
195    /**
196     * Returns the error as XML.
197     *
198     * @return the error as XML.
199     */
200    public String toXML() {
201        StringBuilder buf = new StringBuilder();
202        buf.append("<error code=\"").append(code).append("\"");
203        if (type != null) {
204            buf.append(" type=\"");
205            buf.append(type.name());
206            buf.append("\"");
207        }
208        buf.append(">");
209        if (condition != null) {
210            buf.append("<").append(condition);
211            buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>");
212        }
213        if (message != null) {
214            buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">");
215            buf.append(message);
216            buf.append("</text>");
217        }
218        for (PacketExtension element : this.getExtensions()) {
219            buf.append(element.toXML());
220        }
221        buf.append("</error>");
222        return buf.toString();
223    }
224
225    public String toString() {
226        StringBuilder txt = new StringBuilder();
227        if (condition != null) {
228            txt.append(condition);
229        }
230        txt.append("(").append(code).append(")");
231        if (message != null) {
232            txt.append(" ").append(message);
233        }
234        return txt.toString();
235    }
236
237    /**
238     * Returns an Iterator for the error extensions attached to the xmppError.
239     * An application MAY provide application-specific error information by including a
240     * properly-namespaced child in the error element.
241     *
242     * @return an Iterator for the error extensions.
243     */
244    public synchronized List<PacketExtension> getExtensions() {
245        if (applicationExtensions == null) {
246            return Collections.emptyList();
247        }
248        return Collections.unmodifiableList(applicationExtensions);
249    }
250
251    /**
252     * Returns the first patcket extension that matches the specified element name and
253     * namespace, or <tt>null</tt> if it doesn't exist.
254     *
255     * @param elementName the XML element name of the packet extension.
256     * @param namespace the XML element namespace of the packet extension.
257     * @return the extension, or <tt>null</tt> if it doesn't exist.
258     */
259    public synchronized PacketExtension getExtension(String elementName, String namespace) {
260        if (applicationExtensions == null || elementName == null || namespace == null) {
261            return null;
262        }
263        for (PacketExtension ext : applicationExtensions) {
264            if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
265                return ext;
266            }
267        }
268        return null;
269    }
270
271    /**
272     * Adds a packet extension to the error.
273     *
274     * @param extension a packet extension.
275     */
276    public synchronized void addExtension(PacketExtension extension) {
277        if (applicationExtensions == null) {
278            applicationExtensions = new ArrayList<PacketExtension>();
279        }
280        applicationExtensions.add(extension);
281    }
282
283    /**
284     * Set the packet extension to the error.
285     *
286     * @param extension a packet extension.
287     */
288    public synchronized void setExtension(List<PacketExtension> extension) {
289        applicationExtensions = extension;
290    }
291
292    /**
293     * A class to represent the type of the Error. The types are:
294     *
295     * <ul>
296     *      <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary)
297     *      <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
298     *      <li>XMPPError.Type.MODIFY - retry after changing the data sent
299     *      <li>XMPPError.Type.AUTH - retry after providing credentials
300     *      <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
301     * </ul>
302     */
303    public static enum Type {
304        WAIT,
305        CANCEL,
306        MODIFY,
307        AUTH,
308        CONTINUE
309    }
310
311    /**
312     * A class to represent predefined error conditions.
313     */
314    public static class Condition {
315
316        public static final Condition interna_server_error = new Condition("internal-server-error");
317        public static final Condition forbidden = new Condition("forbidden");
318        public static final Condition bad_request = new Condition("bad-request");
319        public static final Condition conflict = new Condition("conflict");
320        public static final Condition feature_not_implemented = new Condition("feature-not-implemented");
321        public static final Condition gone = new Condition("gone");
322        public static final Condition item_not_found = new Condition("item-not-found");
323        public static final Condition jid_malformed = new Condition("jid-malformed");
324        public static final Condition no_acceptable = new Condition("not-acceptable");
325        public static final Condition not_allowed = new Condition("not-allowed");
326        public static final Condition not_authorized = new Condition("not-authorized");
327        public static final Condition payment_required = new Condition("payment-required");
328        public static final Condition recipient_unavailable = new Condition("recipient-unavailable");
329        public static final Condition redirect = new Condition("redirect");
330        public static final Condition registration_required = new Condition("registration-required");
331        public static final Condition remote_server_error = new Condition("remote-server-error");
332        public static final Condition remote_server_not_found = new Condition("remote-server-not-found");
333        public static final Condition remote_server_timeout = new Condition("remote-server-timeout");
334        public static final Condition resource_constraint = new Condition("resource-constraint");
335        public static final Condition service_unavailable = new Condition("service-unavailable");
336        public static final Condition subscription_required = new Condition("subscription-required");
337        public static final Condition undefined_condition = new Condition("undefined-condition");
338        public static final Condition unexpected_request = new Condition("unexpected-request");
339        public static final Condition request_timeout = new Condition("request-timeout");
340
341        private String value;
342
343        public Condition(String value) {
344            this.value = value;
345        }
346
347        public String toString() {
348            return value;
349        }
350    }
351
352
353    /**
354     * A class to represent the error specification used to infer common usage.
355     */
356    private static class ErrorSpecification {
357        private int code;
358        private Type type;
359        private Condition condition;
360        private static Map<Condition, ErrorSpecification> instances = errorSpecifications();
361
362        private ErrorSpecification(Condition condition, Type type, int code) {
363            this.code = code;
364            this.type = type;
365            this.condition = condition;
366        }
367
368        private static Map<Condition, ErrorSpecification> errorSpecifications() {
369            Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>(22);
370            instances.put(Condition.interna_server_error, new ErrorSpecification(
371                    Condition.interna_server_error, Type.WAIT, 500));
372            instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden,
373                    Type.AUTH, 403));
374            instances.put(Condition.bad_request, new XMPPError.ErrorSpecification(
375                    Condition.bad_request, Type.MODIFY, 400));
376            instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification(
377                    Condition.item_not_found, Type.CANCEL, 404));
378            instances.put(Condition.conflict, new XMPPError.ErrorSpecification(
379                    Condition.conflict, Type.CANCEL, 409));
380            instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification(
381                    Condition.feature_not_implemented, Type.CANCEL, 501));
382            instances.put(Condition.gone, new XMPPError.ErrorSpecification(
383                    Condition.gone, Type.MODIFY, 302));
384            instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification(
385                    Condition.jid_malformed, Type.MODIFY, 400));
386            instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification(
387                    Condition.no_acceptable, Type.MODIFY, 406));
388            instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification(
389                    Condition.not_allowed, Type.CANCEL, 405));
390            instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification(
391                    Condition.not_authorized, Type.AUTH, 401));
392            instances.put(Condition.payment_required, new XMPPError.ErrorSpecification(
393                    Condition.payment_required, Type.AUTH, 402));
394            instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification(
395                    Condition.recipient_unavailable, Type.WAIT, 404));
396            instances.put(Condition.redirect, new XMPPError.ErrorSpecification(
397                    Condition.redirect, Type.MODIFY, 302));
398            instances.put(Condition.registration_required, new XMPPError.ErrorSpecification(
399                    Condition.registration_required, Type.AUTH, 407));
400            instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification(
401                    Condition.remote_server_not_found, Type.CANCEL, 404));
402            instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification(
403                    Condition.remote_server_timeout, Type.WAIT, 504));
404            instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification(
405                    Condition.remote_server_error, Type.CANCEL, 502));
406            instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification(
407                    Condition.resource_constraint, Type.WAIT, 500));
408            instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification(
409                    Condition.service_unavailable, Type.CANCEL, 503));
410            instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification(
411                    Condition.subscription_required, Type.AUTH, 407));
412            instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification(
413                    Condition.undefined_condition, Type.WAIT, 500));
414            instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification(
415                    Condition.unexpected_request, Type.WAIT, 400));
416            instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification(
417                    Condition.request_timeout, Type.CANCEL, 408));
418
419            return instances;
420        }
421
422        protected static ErrorSpecification specFor(Condition condition) {
423            return instances.get(condition);
424        }
425
426        /**
427         * Returns the error condition.
428         *
429         * @return the error condition.
430         */
431        protected Condition getCondition() {
432            return condition;
433        }
434
435        /**
436         * Returns the error type.
437         *
438         * @return the error type.
439         */
440        protected Type getType() {
441            return type;
442        }
443
444        /**
445         * Returns the error code.
446         *
447         * @return the error code.
448         */
449        protected int getCode() {
450            return code;
451        }
452    }
453}
454