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