001/**
002 *
003 * Copyright 2003-2007 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.packet;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025
026import org.jivesoftware.smack.util.StringUtils;
027
028/**
029 * Represents a XMPP error sub-packet. Typically, a server responds to a request that has
030 * problems by sending the packet back and including an error packet. Each error has a type,
031 * error condition as well as as an optional text explanation. Typical errors are:<p>
032 *
033 * <table border=1>
034 *      <hr><td><b>XMPP Error</b></td><td><b>Type</b></td></hr>
035 *      <tr><td>internal-server-error</td><td>WAIT</td></tr>
036 *      <tr><td>forbidden</td><td>AUTH</td></tr>
037 *      <tr><td>bad-request</td><td>MODIFY</td></tr>
038 *      <tr><td>item-not-found</td><td>CANCEL</td></tr>
039 *      <tr><td>conflict</td><td>CANCEL</td></tr>
040 *      <tr><td>feature-not-implemented</td><td>CANCEL</td></tr>
041 *      <tr><td>gone</td><td>MODIFY</td></tr>
042 *      <tr><td>jid-malformed</td><td>MODIFY</td></tr>
043 *      <tr><td>not-acceptable</td><td> MODIFY</td></tr>
044 *      <tr><td>not-allowed</td><td>CANCEL</td></tr>
045 *      <tr><td>not-authorized</td><td>AUTH</td></tr>
046 *      <tr><td>payment-required</td><td>AUTH</td></tr>
047 *      <tr><td>recipient-unavailable</td><td>WAIT</td></tr>
048 *      <tr><td>redirect</td><td>MODIFY</td></tr>
049 *      <tr><td>registration-required</td><td>AUTH</td></tr>
050 *      <tr><td>remote-server-not-found</td><td>CANCEL</td></tr>
051 *      <tr><td>remote-server-timeout</td><td>WAIT</td></tr>
052 *      <tr><td>remote-server-error</td><td>CANCEL</td></tr>
053 *      <tr><td>resource-constraint</td><td>WAIT</td></tr>
054 *      <tr><td>service-unavailable</td><td>CANCEL</td></tr>
055 *      <tr><td>subscription-required</td><td>AUTH</td></tr>
056 *      <tr><td>undefined-condition</td><td>WAIT</td></tr>
057 *      <tr><td>unexpected-condition</td><td>WAIT</td></tr>
058 *      <tr><td>request-timeout</td><td>CANCEL</td></tr>
059 * </table>
060 *
061 * @author Matt Tucker
062 * @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas-error-syntax">RFC 6120 - 8.3.2 Syntax: The Syntax of XMPP error stanzas</a>
063 */
064public class XMPPError {
065
066    private final Type type;
067    private final String condition;
068    private String message;
069    private List<PacketExtension> applicationExtensions = null;
070
071    /**
072     * Creates a new error with the specified condition inferring the type.
073     * If the Condition is predefined, client code should be like:
074     *     new XMPPError(XMPPError.Condition.remote_server_timeout);
075     * If the Condition is not predefined, invocations should be like 
076     *     new XMPPError(new XMPPError.Condition("my_own_error"));
077     * 
078     * @param condition the error condition.
079     */
080    public XMPPError(Condition condition) {
081        // Look for the condition and its default type
082        ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition);
083        this.condition = condition.value;
084        if (defaultErrorSpecification != null) {
085            // If there is a default error specification for the received condition,
086            // it get configured with the inferred type.
087            type = defaultErrorSpecification.getType();
088        } else {
089            type = null;
090        }
091    }
092
093    /**
094     * Creates a new error with the specified condition and message infering the type.
095     * If the Condition is predefined, client code should be like:
096     *     new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation");
097     * If the Condition is not predefined, invocations should be like 
098     *     new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation");
099     *
100     * @param condition the error condition.
101     * @param messageText a message describing the error.
102     */
103    public XMPPError(Condition condition, String messageText) {
104        this(condition);
105        this.message = messageText;
106    }
107
108    /**
109     * Creates a new error with the specified type, condition and message.
110     * This constructor is used when the condition is not recognized automatically by XMPPError
111     * i.e. there is not a defined instance of ErrorCondition or it does not apply the default 
112     * specification.
113     * 
114     * @param type the error type.
115     * @param condition the error condition.
116     * @param message a message describing the error.
117     * @param extension list of packet extensions
118     */
119    public XMPPError(Type type, String condition, String message,
120            List<PacketExtension> extension) {
121        this.type = type;
122        this.condition = condition;
123        this.message = message;
124        this.applicationExtensions = extension;
125    }
126
127    /**
128     * Returns the error condition.
129     *
130     * @return the error condition.
131     */
132    public String getCondition() {
133        return condition;
134    }
135
136    /**
137     * Returns the error type.
138     *
139     * @return the error type.
140     */
141    public Type getType() {
142        return type;
143    }
144
145    /**
146     * Returns the message describing the error, or null if there is no message.
147     *
148     * @return the message describing the error, or null if there is no message.
149     */
150    public String getMessage() {
151        return message;
152    }
153
154    /**
155     * Returns the error as XML.
156     *
157     * @return the error as XML.
158     */
159    public CharSequence toXML() {
160        StringBuilder buf = new StringBuilder();
161        buf.append("<error");
162        if (type != null) {
163            buf.append(" type=\"");
164            buf.append(type.name().toLowerCase(Locale.US));
165            buf.append("\"");
166        }
167        buf.append(">");
168        if (condition != null) {
169            buf.append("<").append(condition);
170            buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>");
171        }
172        if (message != null) {
173            buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">");
174            buf.append(message);
175            buf.append("</text>");
176        }
177        for (PacketExtension element : this.getExtensions()) {
178            buf.append(element.toXML());
179        }
180        buf.append("</error>");
181        return buf.toString();
182    }
183
184    public String toString() {
185        StringBuilder txt = new StringBuilder();
186        if (condition != null) {
187            txt.append(condition);
188        }
189        if (message != null) {
190            txt.append(" ").append(message);
191        }
192        return txt.toString();
193    }
194
195    /**
196     * Returns a List of the error extensions attached to the xmppError.
197     * An application MAY provide application-specific error information by including a 
198     * properly-namespaced child in the error element.
199     *
200     * @return a List of the error extensions.
201     */
202    public synchronized List<PacketExtension> getExtensions() {
203        if (applicationExtensions == null) {
204            return Collections.emptyList();
205        }
206        return Collections.unmodifiableList(applicationExtensions);
207    }
208
209    /**
210     * Returns the first packet extension that matches the specified element name and
211     * namespace, or <tt>null</tt> if it doesn't exist. 
212     *
213     * @param elementName the XML element name of the packet extension.
214     * @param namespace the XML element namespace of the packet extension.
215     * @return the extension, or <tt>null</tt> if it doesn't exist.
216     */
217    public synchronized PacketExtension getExtension(String elementName, String namespace) {
218        if (applicationExtensions == null || elementName == null || namespace == null) {
219            return null;
220        }
221        for (PacketExtension ext : applicationExtensions) {
222            if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
223                return ext;
224            }
225        }
226        return null;
227    }
228
229    /**
230     * Adds a packet extension to the error.
231     *
232     * @param extension a packet extension.
233     */
234    public synchronized void addExtension(PacketExtension extension) {
235        if (applicationExtensions == null) {
236            applicationExtensions = new ArrayList<PacketExtension>();
237        }
238        applicationExtensions.add(extension);
239    }
240
241    /**
242     * Set the packet extension to the error.
243     *
244     * @param extension a packet extension.
245     */
246    public synchronized void setExtension(List<PacketExtension> extension) {
247        applicationExtensions = extension;
248    }
249
250    /**
251     * A class to represent the type of the Error. The types are:
252     *
253     * <ul>
254     *      <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary)
255     *      <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
256     *      <li>XMPPError.Type.MODIFY - retry after changing the data sent
257     *      <li>XMPPError.Type.AUTH - retry after providing credentials
258     *      <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
259     * </ul>
260     */
261    public static enum Type {
262        WAIT,
263        CANCEL,
264        MODIFY,
265        AUTH,
266        CONTINUE
267    }
268
269    /**
270     * A class to represent predefined error conditions.
271     */
272    public static class Condition implements CharSequence {
273
274        public static final Condition internal_server_error = new Condition("internal-server-error");
275        public static final Condition forbidden = new Condition("forbidden");
276        public static final Condition bad_request = new Condition("bad-request");
277        public static final Condition conflict = new Condition("conflict");
278        public static final Condition feature_not_implemented = new Condition("feature-not-implemented");
279        public static final Condition gone = new Condition("gone");
280        public static final Condition item_not_found = new Condition("item-not-found");
281        public static final Condition jid_malformed = new Condition("jid-malformed");
282        public static final Condition not_acceptable = new Condition("not-acceptable");
283        public static final Condition not_allowed = new Condition("not-allowed");
284        public static final Condition not_authorized = new Condition("not-authorized");
285        public static final Condition payment_required = new Condition("payment-required");
286        public static final Condition recipient_unavailable = new Condition("recipient-unavailable");
287        public static final Condition redirect = new Condition("redirect");
288        public static final Condition registration_required = new Condition("registration-required");
289        public static final Condition remote_server_error = new Condition("remote-server-error");
290        public static final Condition remote_server_not_found = new Condition("remote-server-not-found");
291        public static final Condition remote_server_timeout = new Condition("remote-server-timeout");
292        public static final Condition resource_constraint = new Condition("resource-constraint");
293        public static final Condition service_unavailable = new Condition("service-unavailable");
294        public static final Condition subscription_required = new Condition("subscription-required");
295        public static final Condition undefined_condition = new Condition("undefined-condition");
296        public static final Condition unexpected_request = new Condition("unexpected-request");
297        public static final Condition request_timeout = new Condition("request-timeout");
298
299        private final String value;
300
301        public Condition(String value) {
302            this.value = value;
303        }
304
305        @Override 
306        public String toString() {
307            return value;
308        }
309
310        @Override
311        public boolean equals(Object other) {
312            if (other == null) {
313                return false;
314            }
315            return toString().equals(other.toString());
316        }
317
318        public boolean equals(CharSequence other) {
319            return StringUtils.nullSafeCharSequenceEquals(this, other);
320        }
321
322        @Override
323        public int hashCode() {
324            return value.hashCode();
325        }
326
327        @Override
328        public int length() {
329            return value.length();
330        }
331
332        @Override
333        public char charAt(int index) {
334            return value.charAt(index);
335        }
336
337        @Override
338        public CharSequence subSequence(int start, int end) {
339            return value.subSequence(start, end);
340        }
341    }
342
343
344    /**
345     * A class to represent the error specification used to infer common usage.
346     */
347    private static class ErrorSpecification {
348        private static Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>();
349
350        private final Type type;
351        @SuppressWarnings("unused")
352        private final Condition condition;
353
354        private ErrorSpecification(Condition condition, Type type) {
355            this.type = type;
356            this.condition = condition;
357        }
358
359        static {
360            instances.put(Condition.internal_server_error, new ErrorSpecification(
361                    Condition.internal_server_error, Type.WAIT));
362            instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden,
363                    Type.AUTH));
364            instances.put(Condition.bad_request, new XMPPError.ErrorSpecification(
365                    Condition.bad_request, Type.MODIFY));
366            instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification(
367                    Condition.item_not_found, Type.CANCEL));
368            instances.put(Condition.conflict, new XMPPError.ErrorSpecification(
369                    Condition.conflict, Type.CANCEL));
370            instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification(
371                    Condition.feature_not_implemented, Type.CANCEL));
372            instances.put(Condition.gone, new XMPPError.ErrorSpecification(
373                    Condition.gone, Type.MODIFY));
374            instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification(
375                    Condition.jid_malformed, Type.MODIFY));
376            instances.put(Condition.not_acceptable, new XMPPError.ErrorSpecification(
377                    Condition.not_acceptable, Type.MODIFY));
378            instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification(
379                    Condition.not_allowed, Type.CANCEL));
380            instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification(
381                    Condition.not_authorized, Type.AUTH));
382            instances.put(Condition.payment_required, new XMPPError.ErrorSpecification(
383                    Condition.payment_required, Type.AUTH));
384            instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification(
385                    Condition.recipient_unavailable, Type.WAIT));
386            instances.put(Condition.redirect, new XMPPError.ErrorSpecification(
387                    Condition.redirect, Type.MODIFY));
388            instances.put(Condition.registration_required, new XMPPError.ErrorSpecification(
389                    Condition.registration_required, Type.AUTH));
390            instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification(
391                    Condition.remote_server_not_found, Type.CANCEL));
392            instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification(
393                    Condition.remote_server_timeout, Type.WAIT));
394            instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification(
395                    Condition.remote_server_error, Type.CANCEL));
396            instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification(
397                    Condition.resource_constraint, Type.WAIT));
398            instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification(
399                    Condition.service_unavailable, Type.CANCEL));
400            instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification(
401                    Condition.subscription_required, Type.AUTH));
402            instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification(
403                    Condition.undefined_condition, Type.WAIT));
404            instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification(
405                    Condition.unexpected_request, Type.WAIT));
406            instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification(
407                    Condition.request_timeout, Type.CANCEL));
408        }
409
410        protected static ErrorSpecification specFor(Condition condition) {
411            return instances.get(condition);
412        }
413
414        /**
415         * Returns the error type.
416         *
417         * @return the error type.
418         */
419        protected Type getType() {
420            return type;
421        }
422    }
423}