/** * $RCSfile$ * $Revision: 10865 $ * $Date: 2008-11-03 10:28:57 -0600 (Mon, 03 Nov 2008) $ * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smack.packet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Represents a XMPP error sub-packet. Typically, a server responds to a request * that has problems by sending the packet back and including an error packet. * Each error has a code, type, error condition as well as as an optional text * explanation. Typical errors are: * <p> * * <table border=1> * <hr> * <td><b>Code</b></td> * <td><b>XMPP Error</b></td> * <td><b>Type</b></td> * </hr> * <tr> * <td>500</td> * <td>interna-server-error</td> * <td>WAIT</td> * </tr> * <tr> * <td>403</td> * <td>forbidden</td> * <td>AUTH</td> * </tr> * <tr> * <td>400</td * <td>bad-request</td> * <td>MODIFY</td>> * </tr> * <tr> * <td>404</td> * <td>item-not-found</td> * <td>CANCEL</td> * </tr> * <tr> * <td>409</td> * <td>conflict</td> * <td>CANCEL</td> * </tr> * <tr> * <td>501</td> * <td>feature-not-implemented</td> * <td>CANCEL</td> * </tr> * <tr> * <td>302</td> * <td>gone</td> * <td>MODIFY</td> * </tr> * <tr> * <td>400</td> * <td>jid-malformed</td> * <td>MODIFY</td> * </tr> * <tr> * <td>406</td> * <td>no-acceptable</td> * <td>MODIFY</td> * </tr> * <tr> * <td>405</td> * <td>not-allowed</td> * <td>CANCEL</td> * </tr> * <tr> * <td>401</td> * <td>not-authorized</td> * <td>AUTH</td> * </tr> * <tr> * <td>402</td> * <td>payment-required</td> * <td>AUTH</td> * </tr> * <tr> * <td>404</td> * <td>recipient-unavailable</td> * <td>WAIT</td> * </tr> * <tr> * <td>302</td> * <td>redirect</td> * <td>MODIFY</td> * </tr> * <tr> * <td>407</td> * <td>registration-required</td> * <td>AUTH</td> * </tr> * <tr> * <td>404</td> * <td>remote-server-not-found</td> * <td>CANCEL</td> * </tr> * <tr> * <td>504</td> * <td>remote-server-timeout</td> * <td>WAIT</td> * </tr> * <tr> * <td>502</td> * <td>remote-server-error</td> * <td>CANCEL</td> * </tr> * <tr> * <td>500</td> * <td>resource-constraint</td> * <td>WAIT</td> * </tr> * <tr> * <td>503</td> * <td>service-unavailable</td> * <td>CANCEL</td> * </tr> * <tr> * <td>407</td> * <td>subscription-required</td> * <td>AUTH</td> * </tr> * <tr> * <td>500</td> * <td>undefined-condition</td> * <td>WAIT</td> * </tr> * <tr> * <td>400</td> * <td>unexpected-condition</td> * <td>WAIT</td> * </tr> * <tr> * <td>408</td> * <td>request-timeout</td> * <td>CANCEL</td> * </tr> * </table> * * @author Matt Tucker */ public class XMPPError { /** * A class to represent predefined error conditions. */ public static class Condition { public static final Condition interna_server_error = new Condition( "internal-server-error"); public static final Condition forbidden = new Condition("forbidden"); public static final Condition bad_request = new Condition("bad-request"); public static final Condition conflict = new Condition("conflict"); public static final Condition feature_not_implemented = new Condition( "feature-not-implemented"); public static final Condition gone = new Condition("gone"); public static final Condition item_not_found = new Condition( "item-not-found"); public static final Condition jid_malformed = new Condition( "jid-malformed"); public static final Condition no_acceptable = new Condition( "not-acceptable"); public static final Condition not_allowed = new Condition("not-allowed"); public static final Condition not_authorized = new Condition( "not-authorized"); public static final Condition payment_required = new Condition( "payment-required"); public static final Condition recipient_unavailable = new Condition( "recipient-unavailable"); public static final Condition redirect = new Condition("redirect"); public static final Condition registration_required = new Condition( "registration-required"); public static final Condition remote_server_error = new Condition( "remote-server-error"); public static final Condition remote_server_not_found = new Condition( "remote-server-not-found"); public static final Condition remote_server_timeout = new Condition( "remote-server-timeout"); public static final Condition resource_constraint = new Condition( "resource-constraint"); public static final Condition service_unavailable = new Condition( "service-unavailable"); public static final Condition subscription_required = new Condition( "subscription-required"); public static final Condition undefined_condition = new Condition( "undefined-condition"); public static final Condition unexpected_request = new Condition( "unexpected-request"); public static final Condition request_timeout = new Condition( "request-timeout"); private final String value; public Condition(String value) { this.value = value; } @Override public String toString() { return value; } } /** * A class to represent the error specification used to infer common usage. */ private static class ErrorSpecification { private static Map<Condition, ErrorSpecification> errorSpecifications() { final Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>( 22); instances.put(Condition.interna_server_error, new ErrorSpecification(Condition.interna_server_error, Type.WAIT, 500)); instances.put(Condition.forbidden, new ErrorSpecification( Condition.forbidden, Type.AUTH, 403)); instances.put(Condition.bad_request, new XMPPError.ErrorSpecification(Condition.bad_request, Type.MODIFY, 400)); instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification(Condition.item_not_found, Type.CANCEL, 404)); instances.put(Condition.conflict, new XMPPError.ErrorSpecification( Condition.conflict, Type.CANCEL, 409)); instances .put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification( Condition.feature_not_implemented, Type.CANCEL, 501)); instances.put(Condition.gone, new XMPPError.ErrorSpecification( Condition.gone, Type.MODIFY, 302)); instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification(Condition.jid_malformed, Type.MODIFY, 400)); instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification(Condition.no_acceptable, Type.MODIFY, 406)); instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification(Condition.not_allowed, Type.CANCEL, 405)); instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification(Condition.not_authorized, Type.AUTH, 401)); instances.put(Condition.payment_required, new XMPPError.ErrorSpecification( Condition.payment_required, Type.AUTH, 402)); instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification( Condition.recipient_unavailable, Type.WAIT, 404)); instances.put(Condition.redirect, new XMPPError.ErrorSpecification( Condition.redirect, Type.MODIFY, 302)); instances.put(Condition.registration_required, new XMPPError.ErrorSpecification( Condition.registration_required, Type.AUTH, 407)); instances .put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification( Condition.remote_server_not_found, Type.CANCEL, 404)); instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification( Condition.remote_server_timeout, Type.WAIT, 504)); instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification( Condition.remote_server_error, Type.CANCEL, 502)); instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification( Condition.resource_constraint, Type.WAIT, 500)); instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification( Condition.service_unavailable, Type.CANCEL, 503)); instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification( Condition.subscription_required, Type.AUTH, 407)); instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification( Condition.undefined_condition, Type.WAIT, 500)); instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification( Condition.unexpected_request, Type.WAIT, 400)); instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification(Condition.request_timeout, Type.CANCEL, 408)); return instances; } protected static ErrorSpecification specFor(Condition condition) { return instances.get(condition); } private final int code; private final Type type; private static Map<Condition, ErrorSpecification> instances = errorSpecifications(); private ErrorSpecification(Condition condition, Type type, int code) { this.code = code; this.type = type; } /** * Returns the error code. * * @return the error code. */ protected int getCode() { return code; } /** * Returns the error type. * * @return the error type. */ protected Type getType() { return type; } } /** * A class to represent the type of the Error. The types are: * * <ul> * <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary) * <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) * <li>XMPPError.Type.MODIFY - retry after changing the data sent * <li>XMPPError.Type.AUTH - retry after providing credentials * <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning) * </ul> */ public static enum Type { WAIT, CANCEL, MODIFY, AUTH, CONTINUE } private int code; private Type type; private String condition; private final String message; private List<PacketExtension> applicationExtensions = null; /** * Creates a new error with the specified condition infering the type and * code. If the Condition is predefined, client code should be like: new * XMPPError(XMPPError.Condition.remote_server_timeout); If the Condition is * not predefined, invocations should be like new XMPPError(new * XMPPError.Condition("my_own_error")); * * @param condition * the error condition. */ public XMPPError(Condition condition) { init(condition); message = null; } /** * Creates a new error with the specified condition and message infering the * type and code. If the Condition is predefined, client code should be * like: new XMPPError(XMPPError.Condition.remote_server_timeout, * "Error Explanation"); If the Condition is not predefined, invocations * should be like new XMPPError(new XMPPError.Condition("my_own_error"), * "Error Explanation"); * * @param condition * the error condition. * @param messageText * a message describing the error. */ public XMPPError(Condition condition, String messageText) { init(condition); message = messageText; } /** * Creates a new error with the specified code and no message. * * @param code * the error code. * @deprecated new errors should be created using the constructor * XMPPError(condition) */ @Deprecated public XMPPError(int code) { this.code = code; message = null; } /** * Creates a new error with the specified code and message. deprecated * * @param code * the error code. * @param message * a message describing the error. * @deprecated new errors should be created using the constructor * XMPPError(condition, message) */ @Deprecated public XMPPError(int code, String message) { this.code = code; this.message = message; } /** * Creates a new error with the specified code, type, condition and message. * This constructor is used when the condition is not recognized * automatically by XMPPError i.e. there is not a defined instance of * ErrorCondition or it does not applies the default specification. * * @param code * the error code. * @param type * the error type. * @param condition * the error condition. * @param message * a message describing the error. * @param extension * list of packet extensions */ public XMPPError(int code, Type type, String condition, String message, List<PacketExtension> extension) { this.code = code; this.type = type; this.condition = condition; this.message = message; applicationExtensions = extension; } /** * Adds a packet extension to the error. * * @param extension * a packet extension. */ public synchronized void addExtension(PacketExtension extension) { if (applicationExtensions == null) { applicationExtensions = new ArrayList<PacketExtension>(); } applicationExtensions.add(extension); } /** * Returns the error code. * * @return the error code. */ public int getCode() { return code; } /** * Returns the error condition. * * @return the error condition. */ public String getCondition() { return condition; } /** * Returns the first patcket extension that matches the specified element * name and namespace, or <tt>null</tt> if it doesn't exist. * * @param elementName * the XML element name of the packet extension. * @param namespace * the XML element namespace of the packet extension. * @return the extension, or <tt>null</tt> if it doesn't exist. */ public synchronized PacketExtension getExtension(String elementName, String namespace) { if (applicationExtensions == null || elementName == null || namespace == null) { return null; } for (final PacketExtension ext : applicationExtensions) { if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { return ext; } } return null; } /** * Returns an Iterator for the error extensions attached to the xmppError. * An application MAY provide application-specific error information by * including a properly-namespaced child in the error element. * * @return an Iterator for the error extensions. */ public synchronized List<PacketExtension> getExtensions() { if (applicationExtensions == null) { return Collections.emptyList(); } return Collections.unmodifiableList(applicationExtensions); } /** * Returns the message describing the error, or null if there is no message. * * @return the message describing the error, or null if there is no message. */ public String getMessage() { return message; } /** * Returns the error type. * * @return the error type. */ public Type getType() { return type; } /** * Initialize the error infering the type and code for the received * condition. * * @param condition * the error condition. */ private void init(Condition condition) { // Look for the condition and its default code and type final ErrorSpecification defaultErrorSpecification = ErrorSpecification .specFor(condition); this.condition = condition.value; if (defaultErrorSpecification != null) { // If there is a default error specification for the received // condition, // it get configured with the infered type and code. type = defaultErrorSpecification.getType(); code = defaultErrorSpecification.getCode(); } } /** * Set the packet extension to the error. * * @param extension * a packet extension. */ public synchronized void setExtension(List<PacketExtension> extension) { applicationExtensions = extension; } @Override public String toString() { final StringBuilder txt = new StringBuilder(); if (condition != null) { txt.append(condition); } txt.append("(").append(code).append(")"); if (message != null) { txt.append(" ").append(message); } return txt.toString(); } /** * Returns the error as XML. * * @return the error as XML. */ public String toXML() { final StringBuilder buf = new StringBuilder(); buf.append("<error code=\"").append(code).append("\""); if (type != null) { buf.append(" type=\""); buf.append(type.name()); buf.append("\""); } buf.append(">"); if (condition != null) { buf.append("<").append(condition); buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); } if (message != null) { buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"); buf.append(message); buf.append("</text>"); } for (final PacketExtension element : getExtensions()) { buf.append(element.toXML()); } buf.append("</error>"); return buf.toString(); } }