org.xmpp.packet.PacketError Maven / Gradle / Ivy
Show all versions of tinder Show documentation
/**
* Copyright (C) 2004-2009 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.xmpp.packet;
import net.jcip.annotations.NotThreadSafe;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.StringWriter;
import java.util.Iterator;
/**
* A packet error. Errors must have a type and condition. Optionally, they
* can include explanation text.
*
* @author Matt Tucker
*/
@NotThreadSafe
public class PacketError {
private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
private static DocumentFactory docFactory = DocumentFactory.getInstance();
private Element element;
/**
* Construcs 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 PacketError(Condition condition) {
this.element = docFactory.createElement("error");
setCondition(condition);
setType(condition.getDefaultType());
}
/**
* Constructs a new PacketError with the specified condition and type.
*
* @param condition the error condition.
* @param type the error type.
*/
public PacketError(Condition condition, Type type) {
this.element = docFactory.createElement("error");
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 PacketError(Condition condition, Type type, String text) {
this.element = docFactory.createElement("error");
setType(type);
setCondition(condition);
setText(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 PacketError(Condition condition, Type type, String text, String lang) {
this.element = docFactory.createElement("error");
setType(type);
setCondition(condition);
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 PacketError(Element element) {
this.element = element;
}
/**
* Returns the error type.
*
* @return the error type.
* @see Type
*/
public Type getType() {
String type = element.attributeValue("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) {
element.addAttribute("type", type==null?null:type.toXMPP());
}
/**
* Returns the error condition.
*
* @return the error condition.
* @see Condition
*/
@SuppressWarnings("unchecked")
public Condition getCondition() {
for (Iterator i=element.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 = element.attributeValue("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.
element.addAttribute("code", Integer.toString(condition.getLegacyCode()));
Element conditionElement = null;
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
conditionElement = el;
}
}
if (conditionElement != null) {
element.remove(conditionElement);
}
conditionElement = docFactory.createElement(condition.toXMPP(),
ERROR_NAMESPACE);
element.add(conditionElement);
}
/**
* Returns a text description of the error, or null if there
* is no text description.
*
* @return the text description of the error.
*/
public String getText() {
return element.elementText("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 null to specify
* no language code.
*/
public void setText(String text, String lang) {
Element textElement = element.element("text");
// If text is null, clear the text.
if (text == null) {
if (textElement != null) {
element.remove(textElement);
}
return;
}
if (textElement == null) {
textElement = docFactory.createElement("text", ERROR_NAMESPACE);
if (lang != null) {
textElement.addAttribute(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"), lang);
}
element.add(textElement);
}
textElement.setText(text);
}
/**
* Returns the text description's language code, or null 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() {
Element textElement = element.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();
}
Element applicationError = null;
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (!el.getNamespaceURI().equals(ERROR_NAMESPACE))
{
applicationError = el;
}
}
if (applicationError != null) {
element.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";
}
applicationError = docFactory.createElement(name, namespaceURI);
element.add(applicationError);
}
/**
* Returns the name of the application-specific error condition,
* or null if there is no application-specific error.
*
* @return the name of the application-specific error condition, if it exists.
*/
@SuppressWarnings("unchecked")
public String getApplicationConditionName() {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (!el.getNamespaceURI().equals(ERROR_NAMESPACE))
{
return el.getName();
}
}
return null;
}
/**
* Returns the namespace of the application-specific error condition,
* or null if there is no application-specific error.
*
* @return the namespace of the application-specific error condition, if it exists.
*/
@SuppressWarnings("unchecked")
public String getApplicationConditionNamespaceURI() {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = i.next();
if (!el.getNamespaceURI().equals(ERROR_NAMESPACE))
{
return el.getNamespaceURI();
}
}
return null;
}
/**
* Returns the DOM4J Element that backs the error. The element is the definitive
* representation of the error and can be manipulated directly to change
* error contents.
*
* @return the DOM4J Element.
*/
public Element getElement() {
return element;
}
/**
* Returns the textual XML representation of this error.
*
* @return the textual XML representation of this error.
*/
public String toXML() {
return element.asXML();
}
public String toString() {
StringWriter out = new StringWriter();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
try {
writer.write(element);
}
catch (Exception e) {
// Ignore.
}
return out.toString();
}
/**
* Type-safe enumeration for the error condition.
*
* 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 bad_request. 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 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),
/**
* 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.
*
* 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);
/**
* 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();
if (bad_request.toXMPP().equals(condition)) {
return bad_request;
}
else if (conflict.toXMPP().equals(condition)) {
return conflict;
}
else if (feature_not_implemented.toXMPP().equals(condition)) {
return feature_not_implemented;
}
else if (forbidden.toXMPP().equals(condition)) {
return forbidden;
}
else if (gone.toXMPP().equals(condition)) {
return gone;
}
else if (internal_server_error.toXMPP().equals(condition)) {
return internal_server_error;
}
else if (item_not_found.toXMPP().equals(condition)) {
return item_not_found;
}
else if (jid_malformed.toXMPP().equals(condition)) {
return jid_malformed;
}
else if (not_acceptable.toXMPP().equals(condition)) {
return not_acceptable;
}
else if (not_allowed.toXMPP().equals(condition)) {
return not_allowed;
}
else if (not_authorized.toXMPP().equals(condition)) {
return not_authorized;
}
else if (payment_required.toXMPP().equals(condition)) {
return payment_required;
}
else if (recipient_unavailable.toXMPP().equals(condition)) {
return recipient_unavailable;
}
else if (redirect.toXMPP().equals(condition)) {
return redirect;
}
else if (registration_required.toXMPP().equals(condition)) {
return registration_required;
}
else if (remote_server_not_found.toXMPP().equals(condition)) {
return remote_server_not_found;
}
else if (remote_server_timeout.toXMPP().equals(condition)) {
return remote_server_timeout;
}
else if (resource_constraint.toXMPP().equals(condition)) {
return resource_constraint;
}
else if (service_unavailable.toXMPP().equals(condition)) {
return service_unavailable;
}
else if (subscription_required.toXMPP().equals(condition)) {
return subscription_required;
}
else if (undefined_condition.toXMPP().equals(condition)) {
return undefined_condition;
}
else if (unexpected_request.toXMPP().equals(condition)) {
return unexpected_request;
}
else {
throw new IllegalArgumentException("Condition invalid:" + 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 XEP-0086: Error Condition Mappings
*/
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:
*
* - {@link #cancel Error.Type.cancel} -- do not retry (the error is unrecoverable).
*
- {@link #continue_processing Error.Type.continue_processing} -- proceed
* (the condition was only a warning). Equivalent to the XMPP error type
* "continue".
*
- {@link #modify Error.Type.modify} -- retry after changing the data sent.
*
- {@link #auth Eror.Type.auth} -- retry after providing credentials.
*
- {@link #wait Error.Type.wait} -- retry after waiting (the error is temporary).
*
*
* Implementation note: one of the XMPP error types is "continue". Because "continue"
* is a reserved Java keyword, the enum name is continue_processing. 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;
}
}
}