/**
*
* Copyright 2003-2007 Jive Software, 2015 Florian Schmaus
*
* 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.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* Represents an XMPP error sub-packet. Typically, a server responds to a request that has
* problems by sending the stanza(/packet) back and including an error packet. Each error has a type,
* error condition as well as as an optional text explanation. Typical errors are:
*
*
* XMPP Errors
*
XMPP Error Condition | Type | RFC 6120 Section |
* bad-request | MODIFY | 8.3.3.1 |
* conflict | CANCEL | 8.3.3.2 |
* feature-not-implemented | CANCEL | 8.3.3.3 |
* forbidden | AUTH | 8.3.3.4 |
* gone | MODIFY | 8.3.3.5 |
* internal-server-error | WAIT | 8.3.3.6 |
* item-not-found | CANCEL | 8.3.3.7 |
* jid-malformed | MODIFY | 8.3.3.8 |
* not-acceptable | MODIFY | 8.3.3.9 |
* not-allowed | CANCEL | 8.3.3.10 |
* not-authorized | AUTH | 8.3.3.11 |
* policy-violation | AUTH | 8.3.3.12 |
* recipient-unavailable | WAIT | 8.3.3.13 |
* redirect | MODIFY | 8.3.3.14 |
* registration-required | AUTH | 8.3.3.15 |
* remote-server-not-found | CANCEL | 8.3.3.16 |
* remote-server-timeout | WAIT | 8.3.3.17 |
* resource-constraint | WAIT | 8.3.3.18 |
* service-unavailable | CANCEL | 8.3.3.19 |
* subscription-required | AUTH | 8.3.3.20 |
* undefined-condition | WAIT | 8.3.3.21 |
* unexpected-request | WAIT | 8.3.3.22 |
*
*
* @author Matt Tucker
* @see RFC 6120 - 8.3.2 Syntax: The Syntax of XMPP error stanzas
*/
public class XMPPError extends AbstractError {
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
public static final String ERROR = "error";
private static final Logger LOGGER = Logger.getLogger(XMPPError.class.getName());
private static final Map CONDITION_TO_TYPE = new HashMap();
static {
CONDITION_TO_TYPE.put(Condition.bad_request, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.conflict, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.feature_not_implemented, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.forbidden, Type.AUTH);
CONDITION_TO_TYPE.put(Condition.gone, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.internal_server_error, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.item_not_found, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.jid_malformed, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.not_acceptable, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.not_allowed, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.not_authorized, Type.AUTH);
CONDITION_TO_TYPE.put(Condition.policy_violation, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.recipient_unavailable, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.redirect, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.registration_required, Type.AUTH);
CONDITION_TO_TYPE.put(Condition.remote_server_not_found, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.remote_server_timeout, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.resource_constraint, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.subscription_required, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.MODIFY);
}
private final Condition condition;
private final String conditionText;
private final String errorGenerator;
private final Type type;
private final Stanza stanza;
// TODO: Deprecated constructors
// deprecate in 4.3
/**
* Create a new XMPPError.
*
* @param condition
* @deprecated use {@link Builder} instead.
*/
@Deprecated
public XMPPError(Condition condition) {
this(condition, null, null, null, null, null, null);
}
/**
* Create a new XMPPError.
*
* @param condition
* @param applicationSpecificCondition
* @deprecated use {@link Builder} instead.
*/
@Deprecated
public XMPPError(Condition condition, ExtensionElement applicationSpecificCondition) {
this(condition, null, null, null, null, Arrays.asList(applicationSpecificCondition), null);
}
/**
* Creates a new error with the specified 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 apply the default
* specification.
*
* @param type the error type.
* @param condition the error condition.
* @param descriptiveTexts
* @param extensions list of stanza(/packet) extensions
* @deprecated use {@link Builder} instead.
*/
@Deprecated
public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map descriptiveTexts,
List extensions) {
this(condition, conditionText, errorGenerator, type, descriptiveTexts, extensions, null);
}
/**
* Creates a new error with the specified 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 apply the default
* specification.
*
* @param type the error type.
* @param condition the error condition.
* @param descriptiveTexts
* @param extensions list of stanza(/packet) extensions
* @param stanza the stanza carrying this XMPP error.
*/
public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map descriptiveTexts,
List extensions, Stanza stanza) {
super(descriptiveTexts, NAMESPACE, extensions);
this.condition = Objects.requireNonNull(condition, "condition must not be null");
this.stanza = stanza;
// Some implementations may send the condition as non-empty element containing the empty string, that is
// , in this case the parser may calls this constructor with the empty string
// as conditionText, therefore reset it to null if it's the empty string
if (StringUtils.isNullOrEmpty(conditionText)) {
conditionText = null;
}
if (conditionText != null) {
switch (condition) {
case gone:
case redirect:
break;
default:
throw new IllegalArgumentException(
"Condition text can only be set with condtion types 'gone' and 'redirect', not "
+ condition);
}
}
this.conditionText = conditionText;
this.errorGenerator = errorGenerator;
if (type == null) {
Type determinedType = CONDITION_TO_TYPE.get(condition);
if (determinedType == null) {
LOGGER.warning("Could not determine type for condition: " + condition);
determinedType = Type.CANCEL;
}
this.type = determinedType;
} else {
this.type = type;
}
}
/**
* Returns the error condition.
*
* @return the error condition.
*/
public Condition getCondition() {
return condition;
}
/**
* Returns the error type.
*
* @return the error type.
*/
public Type getType() {
return type;
}
public String getErrorGenerator() {
return errorGenerator;
}
public String getConditionText() {
return conditionText;
}
/**
* Get the stanza carrying the XMPP error.
*
* @return the stanza carrying the XMPP error.
* @since 4.2
*/
public Stanza getStanza() {
return stanza;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("XMPPError: ");
sb.append(condition.toString()).append(" - ").append(type.toString());
if (errorGenerator != null) {
sb.append(". Generated by ").append(errorGenerator);
}
return sb.toString();
}
/**
* Returns the error as XML.
*
* @return the error as XML.
*/
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(ERROR);
xml.attribute("type", type.toString());
xml.optAttribute("by", errorGenerator);
xml.rightAngleBracket();
xml.halfOpenElement(condition.toString());
xml.xmlnsAttribute(NAMESPACE);
if (conditionText != null) {
xml.rightAngleBracket();
xml.escape(conditionText);
xml.closeElement(condition.toString());
}
else {
xml.closeEmptyElement();
}
addDescriptiveTextsAndExtensions(xml);
xml.closeElement(ERROR);
return xml;
}
public static XMPPError.Builder from(Condition condition, String descriptiveText) {
Map descriptiveTexts = new HashMap();
descriptiveTexts.put("en", descriptiveText);
return getBuilder().setCondition(condition).setDescriptiveTexts(descriptiveTexts);
}
public static Builder getBuilder() {
return new Builder();
}
public static Builder getBuilder(Condition condition) {
return getBuilder().setCondition(condition);
}
public static Builder getBuilder(XMPPError xmppError) {
return getBuilder().copyFrom(xmppError);
}
public static final class Builder extends AbstractError.Builder {
private Condition condition;
private String conditionText;
private String errorGenerator;
private Type type;
private Stanza stanza;
private Builder() {
}
public Builder setCondition(Condition condition) {
this.condition = condition;
return this;
}
public Builder setType(Type type) {
this.type = type;
return this;
}
public Builder setConditionText(String conditionText) {
this.conditionText = conditionText;
return this;
}
public Builder setErrorGenerator(String errorGenerator) {
this.errorGenerator = errorGenerator;
return this;
}
public Builder setStanza(Stanza stanza) {
this.stanza = stanza;
return this;
}
public Builder copyFrom(XMPPError xmppError) {
setCondition(xmppError.getCondition());
setType(xmppError.getType());
setConditionText(xmppError.getConditionText());
setErrorGenerator(xmppError.getErrorGenerator());
setStanza(xmppError.getStanza());
setDescriptiveTexts(xmppError.descriptiveTexts);
setTextNamespace(xmppError.textNamespace);
setExtensions(xmppError.extensions);
return this;
}
public XMPPError build() {
return new XMPPError(condition, conditionText, errorGenerator, type, descriptiveTexts,
extensions, stanza);
}
@Override
protected Builder getThis() {
return this;
}
}
/**
* A class to represent the type of the Error. The types are:
*
*
* - XMPPError.Type.WAIT - retry after waiting (the error is temporary)
*
- XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
*
- XMPPError.Type.MODIFY - retry after changing the data sent
*
- XMPPError.Type.AUTH - retry after providing credentials
*
- XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
*
*/
public static enum Type {
WAIT,
CANCEL,
MODIFY,
AUTH,
CONTINUE;
@Override
public String toString() {
return name().toLowerCase(Locale.US);
}
public static Type fromString(String string) {
string = string.toUpperCase(Locale.US);
return Type.valueOf(string);
}
}
public enum Condition {
bad_request,
conflict,
feature_not_implemented,
forbidden,
gone,
internal_server_error,
item_not_found,
jid_malformed,
not_acceptable,
not_allowed,
not_authorized,
policy_violation,
recipient_unavailable,
redirect,
registration_required,
remote_server_not_found,
remote_server_timeout,
resource_constraint,
service_unavailable,
subscription_required,
undefined_condition,
unexpected_request;
@Override
public String toString() {
return this.name().replace('_', '-');
}
public static Condition fromString(String string) {
// Backwards compatibility for older implementations still using RFC 3920. RFC 6120
// changed 'xml-not-well-formed' to 'not-well-formed'.
if ("xml-not-well-formed".equals(string)) {
string = "not-well-formed";
}
string = string.replace('-', '_');
Condition condition = null;
try {
condition = Condition.valueOf(string);
} catch (Exception e) {
throw new IllegalStateException("Could not transform string '" + string + "' to XMPPErrorCondition", e);
}
return condition;
}
}
}