package com.rayo.client.xmpp.stanza;
import java.util.Iterator;
import org.dom4j.Element;
import org.dom4j.QName;
/**
* A packet error. Errors must have a type and condition. Optionally, they
* can include explanation text.
*
* @author martin
* @author Based on work done originally by Matt Tucker for the project Tinder from OpenFire
*/
public class Error extends AbstractXmppObject {
private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
public static final String NAME = "error";
/**
* Constructs a new PacketError with the specified condition. The error
* type will be set to the default for the specified condition.
*
* @param condition the error condition.
*/
public Error(Condition condition) {
this(condition, condition.getDefaultType());
}
/**
* Constructs a new PacketError with the specified condition and type.
*
* @param condition the error condition.
* @param type the error type.
*/
public Error(Condition condition, Type type) {
setCondition(condition);
setType(type);
}
/**
* Constructs a new PacketError.
*
* @param type the error type.
* @param condition the error condition.
* @param text the text description of the error.
*/
public Error(Condition condition, Type type, String text) {
this(condition,type,text,null);
}
/**
* Constructs a new PacketError.
*
* @param type the error type.
* @param condition the error condition.
* @param text the text description of the error.
* @param lang the language code of the error description (e.g. "en").
*/
public Error(Condition condition, Type type, String text, String lang) {
this(condition,type);
setText(text, lang);
}
/**
* Constructs a new PacketError using an existing Element. This is useful
* for parsing incoming error Elements into PacketError objects.
*
* @param element the error Element.
*/
public Error(Element element) {
super(element);
}
/**
* Returns the error type.
*
* @return the error type.
* @see Type
*/
public Type getType() {
String type = attribute("type");
if (type != null) {
return Type.fromXMPP(type);
}
else {
return null;
}
}
/**
* Sets the error type.
*
* @param type the error type.
* @see Type
*/
public void setType(Type type) {
setAttribute("type", type==null?null:type.toXMPP());
}
/**
* Returns the error condition.
*
* @return the error condition.
* @see Condition
*/
@SuppressWarnings("unchecked")
public Condition getCondition() {
//TODO: Remote this dependency on getElement()
for (Iterator<Element> i=getElement().elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
return Condition.fromXMPP(el.getName());
}
}
// Looking for XMPP condition failed. See if a legacy error code exists,
// which can be mapped into an XMPP error condition.
String code = attribute("code");
if (code != null) {
try {
return Condition.fromLegacyCode(Integer.parseInt(code));
}
catch (Exception e) {
// Ignore -- unable to map legacy code into a valid condition
// so return null.
}
}
return null;
}
/**
* Sets the error condition.
*
* @param condition the error condition.
* @see Condition
*/
@SuppressWarnings("unchecked")
public void setCondition(Condition condition) {
if (condition == null) {
throw new NullPointerException("Condition cannot be null");
}
// Set the error code for legacy support.
setAttribute("code", Integer.toString(condition.getLegacyCode()));
//TODO: Remove this dependencies on getElement()
Element conditionElement = null;
for (Iterator<Element> i=getElement().elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
conditionElement = el;
}
}
if (conditionElement != null) {
getElement().remove(conditionElement);
}
set(condition.toXMPP(),null,ERROR_NAMESPACE);
}
/**
* Returns a text description of the error, or <tt>null</tt> if there
* is no text description.
*
* @return the text description of the error.
*/
public String getText() {
return value("text");
}
/**
* Sets the text description of the error.
*
* @param text the text description of the error.
*/
public void setText(String text) {
setText(text, null);
}
/**
* Sets the text description of the error. Optionally, a language code
* can be specified to indicate the language of the description.
*
* @param text the text description of the error.
* @param lang the language code of the description, or <tt>null</tt> to specify
* no language code.
*/
public void setText(String text, String lang) {
set("text", text, ERROR_NAMESPACE);
//TODO: Remove this dependency on getElement
if (lang != null) {
Element textElement = getElement().element("text");
textElement.addAttribute(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"), lang);
}
}
/**
* Returns the text description's language code, or <tt>null</tt> if there
* is no language code associated with the description text.
*
* @return the language code of the text description, if it exists.
*/
public String getTextLang() {
//TODO: Remove this dependency on getElement
Element textElement = getElement().element("text");
if (textElement != null) {
return textElement.attributeValue(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"));
}
return null;
}
/**
* Sets an application-specific error condition.
*
* @param name the name of the application-specific error condition.
*/
public void setApplicationCondition(String name) {
setApplicationCondition(name, null);
}
/**
* Sets an application-specific error condition. Optionally, a
* application-specific namespace can be specified to define its
* own application-specific error .
*
* @param name the name of the application-specific error condition.
* @param namespaceURI the namespace of the application.
*/
@SuppressWarnings("unchecked")
public void setApplicationCondition(String name, String namespaceURI) {
if (ERROR_NAMESPACE.equals(namespaceURI)) {
throw new IllegalArgumentException();
}
//TODO: Remove this dependency on getElement()
Element applicationError = null;
for (Iterator<Element> i=getElement().elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (!el.getNamespaceURI().equals(ERROR_NAMESPACE))
{
applicationError = el;
}
}
if (applicationError != null) {
getElement().remove(applicationError);
}
// If name is null, clear the application condition.
if (name == null) {
return;
}
if (namespaceURI == null) {
// Set fallback namespace (see XEP-0182)
namespaceURI = "urn:xmpp:errors";
}
set(name,null,namespaceURI);
}
@Override
public String getStanzaName() {
return "error";
}
@Override
public XmppObject copy() {
return new Error(getElement().createCopy());
}
/**
* Type-safe enumeration for the error condition.<p>
*
* Implementation note: XMPP error conditions use "-" characters in
* their names such as "bad-request". Because "-" characters are not valid
* identifier parts in Java, they have been converted to "_" characters in
* the enumeration names, such as <tt>bad_request</tt>. The {@link #toXMPP()} and
* {@link #fromXMPP(String)} methods can be used to convert between the
* enumertation values and XMPP error code strings.
*/
public enum Condition {
/**
* The sender has sent XML that is malformed or that cannot be processed
* (e.g., an IQ stanza that includes an unrecognized value of the 'type'
* attribute); the associated error type SHOULD be "modify".
*/
bad_request("bad-request", Type.modify, 400),
/**
* Access cannot be granted because an existing resource or session
* exists with the same name or address; the associated error type
* SHOULD be "cancel".
*/
conflict("conflict", Type.cancel, 409),
/**
* The feature requested is not implemented by the recipient or
* server and therefore cannot be processed; the associated error
* type SHOULD be "cancel".
*/
feature_not_implemented("feature-not-implemented", Type.cancel, 501),
/**
* The requesting entity does not possess the required permissions to
* perform the action; the associated error type SHOULD be "auth".
*/
forbidden("forbidden", Type.auth, 403),
/**
* The recipient or server can no longer be contacted at this address
* (the error stanza MAY contain a new address in the XML character
* data of the <gone/> element); the associated error type SHOULD be
* "modify".
*/
gone("gone", Type.modify, 302),
/**
* The server could not process the stanza because of a misconfiguration
* or an otherwise-undefined internal server error; the associated error
* type SHOULD be "wait".
*/
internal_server_error("internal-server-error", Type.wait, 500),
/**
* The addressed JID or item requested cannot be found; the associated
* error type SHOULD be "cancel".
*/
item_not_found("item-not-found", Type.cancel, 404),
/**
* The sending entity has provided or communicated an XMPP address
* (e.g., a value of the 'to' attribute) or aspect thereof (e.g.,
* a resource identifier) that does not adhere to the syntax defined
* in Addressing Scheme (Section 3); the associated error type SHOULD
* be "modify".
*/
jid_malformed("jid-malformed", Type.modify, 400),
/**
* The recipient or server understands the request but is refusing
* to process it because it does not meet criteria defined by the
* recipient or server (e.g., a local policy regarding acceptable
* words in messages); the associated error type SHOULD be "modify".
*/
not_acceptable("not-acceptable", Type.modify, 406),
/**
* The recipient or server does not allow any entity to perform
* the action; the associated error type SHOULD be "cancel".
*/
not_allowed("not-allowed", Type.cancel, 405),
/**
* The sender must provide proper credentials before being allowed
* to perform the action, or has provided improper credentials;
* the associated error type SHOULD be "auth".
*/
not_authorized("not-authorized", Type.auth, 401),
/**
* The requesting entity is not authorized to access the requested
* service because payment is required; the associated error type
* SHOULD be "auth".
*/
payment_required("payment-required", Type.auth, 402),
/**
* The intended recipient is temporarily unavailable; the associated
* error type SHOULD be "wait" (note: an application MUST NOT return
* this error if doing so would provide information about the intended
* recipient's network availability to an entity that is not authorized
* to know such information).
*/
recipient_unavailable("recipient-unavailable", Type.wait, 404),
/**
* The recipient or server is redirecting requests for this
* information to another entity, usually temporarily (the error
* stanza SHOULD contain the alternate address, which MUST be a
* valid JID, in the XML character data of the <redirect/> element);
* the associated error type SHOULD be "modify".
*/
redirect("redirect", Type.modify, 302),
/**
* The requesting entity is not authorized to access the requested
* service because registration is required; the associated error
* type SHOULD be "auth".
*/
registration_required("registration-required", Type.auth, 407),
remote_server_error("remote-server-error", Type.cancel, 502),
/**
* A remote server or service specified as part or all of the JID
* of the intended recipient does not exist; the associated error
* type SHOULD be "cancel".
*/
remote_server_not_found("remote-server-not-found", Type.cancel, 404),
/**
* A remote server or service specified as part or all of the JID of
* the intended recipient (or required to fulfill a request) could not
* be contacted within a reasonable amount of time; the associated
* error type SHOULD be "wait".
*/
remote_server_timeout("remote-server-timeout", Type.wait, 504),
/**
* The server or recipient lacks the system resources necessary to
* service the request; the associated error type SHOULD be "wait".
*/
resource_constraint("resource-constraint", Type.wait, 500),
/**
* The server or recipient does not currently provide the requested
* service; the associated error type SHOULD be "cancel".
*/
service_unavailable("service-unavailable", Type.cancel, 503),
/**
* The requesting entity is not authorized to access the requested
* service because a subscription is required; the associated error
* type SHOULD be "auth".
*/
subscription_required("subscription-required", Type.auth, 407),
/**
* The error condition is not one of those defined by the other
* conditions in this list; any error type may be associated with
* this condition, and it SHOULD be used only in conjunction with
* an application-specific condition.<p>
*
* Implementation note: the default type for this condition is
* {@link Type#wait}, which is not specified in the XMPP protocol.
*/
undefined_condition("undefined-condition", Type.wait, 500),
/**
* The recipient or server understood the request but was not
* expecting it at this time (e.g., the request was out of order);
* the associated error type SHOULD be "wait".
*/
unexpected_request("unexpected-request", Type.wait, 400),
stream_invalid_from("invalid-from", Type.cancel, 0),
stream_bad_format("bad-format", Type.cancel, 0),
stream_bad_namespace_prefix("bad-namespace-prefix", Type.cancel, 0),
stream_conflict("conflict", Type.cancel, 0),
stream_connection_timeout("connection-timeout", Type.cancel, 0),
stream_host_gone("host-gone", Type.cancel, 0),
stream_host_unknown("host-unknown", Type.cancel, 0),
stream_improper_addressing("improper-addressing", Type.cancel, 0),
stream_internal_server_error("internal-server-error", Type.cancel, 0),
stream_invalid_id("invalid-id", Type.cancel, 0),
stream_invalid_namespace("invalid-namespace", Type.cancel, 0),
stream_invalid_xml("invalid-xml", Type.cancel, 0),
stream_not_authorized("not-authorized", Type.cancel, 0),
stream_policy_violation("policy-violation", Type.cancel, 0),
stream_resource_constraint("resource-constraint", Type.cancel, 0),
stream_restricted_xml("restricted-xml", Type.cancel, 0),
stream_see_other_host("see-other-host", Type.cancel, 0),
stream_system_shutdown("system-shutdown", Type.cancel, 0),
stream_undefined("undefined", Type.cancel, 0),
stream_unsupported_encoding("unsupported-encoding", Type.cancel, 0),
stream_unsupported_stanza_type("unsupported-stanza-type", Type.cancel, 0),
stream_unsupported_version("unsupported-version", Type.cancel, 0),
stream_xml_not_well_formed("xml-not-well-formed", Type.cancel, 0);
/**
* Converts a String value into its Condition representation.
*
* @param condition the String value.
* @return the condition corresponding to the String.
*/
public static Condition fromXMPP(String condition) {
if (condition == null) {
throw new NullPointerException();
}
condition = condition.toLowerCase();
for (Condition it: Error.Condition.values()) {
if (it.toXMPP().equals(condition)) {
return it;
}
}
throw new IllegalArgumentException(String.format("Condition not found %s", condition));
}
public static Condition fromLegacyCode(int code) {
if (bad_request.getLegacyCode() == code) {
return bad_request;
}
else if (conflict.getLegacyCode() == code) {
return conflict;
}
else if (feature_not_implemented.getLegacyCode() == code) {
return feature_not_implemented;
}
else if (forbidden.getLegacyCode() == code) {
return forbidden;
}
else if (gone.getLegacyCode() == code) {
return gone;
}
else if (internal_server_error.getLegacyCode() == code) {
return internal_server_error;
}
else if (item_not_found.getLegacyCode() == code) {
return item_not_found;
}
else if (jid_malformed.getLegacyCode() == code) {
return jid_malformed;
}
else if (not_acceptable.getLegacyCode() == code) {
return not_acceptable;
}
else if (not_allowed.getLegacyCode() == code) {
return not_allowed;
}
else if (not_authorized.getLegacyCode() == code) {
return not_authorized;
}
else if (payment_required.getLegacyCode() == code) {
return payment_required;
}
else if (recipient_unavailable.getLegacyCode() == code) {
return recipient_unavailable;
}
else if (redirect.getLegacyCode() == code) {
return redirect;
}
else if (registration_required.getLegacyCode() == code) {
return registration_required;
}
else if (remote_server_not_found.getLegacyCode() == code) {
return remote_server_not_found;
}
else if (remote_server_timeout.getLegacyCode() == code) {
return remote_server_timeout;
}
else if (resource_constraint.getLegacyCode() == code) {
return resource_constraint;
}
else if (service_unavailable.getLegacyCode() == code) {
return service_unavailable;
}
else if (subscription_required.getLegacyCode() == code) {
return subscription_required;
}
else if (undefined_condition.getLegacyCode() == code) {
return undefined_condition;
}
else if (unexpected_request.getLegacyCode() == code) {
return unexpected_request;
}
else {
throw new IllegalArgumentException("Code invalid:" + code);
}
}
private String value;
private int code;
private Type defaultType;
private Condition(String value, Type defaultType, int code) {
this.value = value;
this.defaultType = defaultType;
this.code = code;
}
/**
* Returns the default {@link Type} associated with this condition. Each
* error condition has an error type that it is usually associated with.
*
* @return the default error type.
*/
public Type getDefaultType() {
return defaultType;
}
/**
* Returns the legacy error code associated with the error. Error code mappings
* are based on XEP-0086 'Error Condition Mappings'.
* Support for legacy error codes is necessary since many "Jabber" clients
* do not understand XMPP error codes. The {@link #fromLegacyCode(int)} method
* will convert numeric error codes into Conditions.
*
* @return the legacy error code.
* @see <a href="http://xmpp.org/extensions/xep-0086.html">XEP-0086: Error Condition Mappings</a>
*/
public int getLegacyCode() {
return code;
}
/**
* Returns the error code as a valid XMPP error code string.
*
* @return the XMPP error code value.
*/
public String toXMPP() {
return value;
}
}
/**
* Error type. Valid types are:<ul>
*
* <li>{@link #cancel Error.Type.cancel} -- do not retry (the error is unrecoverable).
* <li>{@link #continue_processing Error.Type.continue_processing} -- proceed
* (the condition was only a warning). Equivalent to the XMPP error type
* "continue".
* <li>{@link #modify Error.Type.modify} -- retry after changing the data sent.
* <li>{@link #auth Eror.Type.auth} -- retry after providing credentials.
* <li>{@link #wait Error.Type.wait} -- retry after waiting (the error is temporary).
* </ul>
*
* Implementation note: one of the XMPP error types is "continue". Because "continue"
* is a reserved Java keyword, the enum name is <tt>continue_processing</tt>. The
* {@link #toXMPP()} and {@link #fromXMPP(String)} methods can be used to convert
* between the enumertation values and XMPP error type strings.
*/
public enum Type {
/**
* Do not retry (the error is unrecoverable).
*/
cancel("cancel"),
/**
* Proceed (the condition was only a warning). This represents
* the "continue" error code in XMPP; because "continue" is a
* reserved keyword in Java the enum name has been changed.
*/
continue_processing("continue"),
/**
* Retry after changing the data sent.
*/
modify("modify"),
/**
* Retry after providing credentials.
*/
auth("auth"),
/**
* Retry after waiting (the error is temporary).
*/
wait("wait");
/**
* Converts a String value into its Type representation.
*
* @param type the String value.
* @return the condition corresponding to the String.
*/
public static Type fromXMPP(String type) {
if (type == null) {
throw new NullPointerException();
}
type = type.toLowerCase();
if (cancel.toXMPP().equals(type)) {
return cancel;
}
else if (continue_processing.toXMPP().equals(type)) {
return continue_processing;
}
else if (modify.toXMPP().equals(type)) {
return modify;
}
else if (auth.toXMPP().equals(type)) {
return auth;
}
else if (wait.toXMPP().equals(type)) {
return wait;
}
else {
throw new IllegalArgumentException("Type invalid:" + type);
}
}
private String value;
private Type(String value) {
this.value = value;
}
/**
* Returns the error code as a valid XMPP error code string.
*
* @return the XMPP error code value.
*/
public String toXMPP() {
return value;
}
}
}