/**
* $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();
}
}