1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-26 13:24:49 +02:00
Smack/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaError.java
Florian Schmaus 3d4e7938a7 Make ExtensionElement marker interface wrt. QNAME field
ExtensionElement is now a marker interface that requires all
implementation non-abstract classes to carry a static final QNAME
field (of type QName). This is verified by a new unit test.

Also FullyQualifiedElement is renamed to simply XmlElement. XmlElement
is used over ExtensionElement when implementing classes do not
statically know the qualified name of the XML elements they
represent. In general, XmlElement should be used sparingly, and every
XML element should be modeled by its own Java class (implementing
ExtensionElement).
2021-04-18 21:07:19 +02:00

381 lines
14 KiB
Java

/**
*
* Copyright 2003-2007 Jive Software, 2015-2021 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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
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 back and including an error packet. Each error has a type,
* error condition as well as as an optional text explanation. Typical errors are:
*
* <table border=1>
* <caption>XMPP Errors</caption>
* <tr><th>XMPP Error Condition</th><th>Type</th><th>RFC 6120 Section</th></tr>
* <tr><td>bad-request</td><td>MODIFY</td><td>8.3.3.1</td></tr>
* <tr><td>conflict</td><td>CANCEL</td><td>8.3.3.2</td></tr>
* <tr><td>feature-not-implemented</td><td>CANCEL</td><td>8.3.3.3</td></tr>
* <tr><td>forbidden</td><td>AUTH</td><td>8.3.3.4</td></tr>
* <tr><td>gone</td><td>CANCEL</td><td>8.3.3.5</td></tr>
* <tr><td>internal-server-error</td><td>WAIT</td><td>8.3.3.6</td></tr>
* <tr><td>item-not-found</td><td>CANCEL</td><td>8.3.3.7</td></tr>
* <tr><td>jid-malformed</td><td>MODIFY</td><td>8.3.3.8</td></tr>
* <tr><td>not-acceptable</td><td>MODIFY</td><td>8.3.3.9</td></tr>
* <tr><td>not-allowed</td><td>CANCEL</td><td>8.3.3.10</td></tr>
* <tr><td>not-authorized</td><td>AUTH</td><td>8.3.3.11</td></tr>
* <tr><td>policy-violation</td><td>MODIFY</td><td>8.3.3.12</td></tr>
* <tr><td>recipient-unavailable</td><td>WAIT</td><td>8.3.3.13</td></tr>
* <tr><td>redirect</td><td>MODIFY</td><td>8.3.3.14</td></tr>
* <tr><td>registration-required</td><td>AUTH</td><td>8.3.3.15</td></tr>
* <tr><td>remote-server-not-found</td><td>CANCEL</td><td>8.3.3.16</td></tr>
* <tr><td>remote-server-timeout</td><td>WAIT</td><td>8.3.3.17</td></tr>
* <tr><td>resource-constraint</td><td>WAIT</td><td>8.3.3.18</td></tr>
* <tr><td>service-unavailable</td><td>CANCEL</td><td>8.3.3.19</td></tr>
* <tr><td>subscription-required</td><td>AUTH</td><td>8.3.3.20</td></tr>
* <tr><td>undefined-condition</td><td>MODIFY</td><td>8.3.3.21</td></tr>
* <tr><td>unexpected-request</td><td>WAIT</td><td>8.3.3.22</td></tr>
* </table>
*
* @author Matt Tucker
* @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas-error-syntax">RFC 6120 - 8.3.2 Syntax: The Syntax of XMPP error stanzas</a>
*/
// TODO Use StanzaErrorTextElement here.
public class StanzaError extends AbstractError implements ExtensionElement {
public static final String ERROR_CONDITION_AND_TEXT_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
/**
* TODO describe me.
*/
@Deprecated
public static final String NAMESPACE = ERROR_CONDITION_AND_TEXT_NAMESPACE;
public static final String ERROR = "error";
public static final QName QNAME = new QName(StreamOpen.CLIENT_NAMESPACE, ERROR);
private static final Logger LOGGER = Logger.getLogger(StanzaError.class.getName());
static final Map<Condition, Type> CONDITION_TO_TYPE = new HashMap<Condition, Type>();
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.CANCEL);
CONDITION_TO_TYPE.put(Condition.subscription_required, Type.AUTH);
CONDITION_TO_TYPE.put(Condition.undefined_condition, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.WAIT);
}
private final Condition condition;
private final String conditionText;
private final String errorGenerator;
private final Type type;
/**
* 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 conditionText TODO javadoc me please
* @param errorGenerator TODO javadoc me please
* @param descriptiveTexts TODO javadoc me please
* @param extensions list of stanza extensions
*/
public StanzaError(Condition condition, String conditionText, String errorGenerator, Type type, Map<String, String> descriptiveTexts,
List<XmlElement> extensions) {
super(descriptiveTexts, ERROR_CONDITION_AND_TEXT_NAMESPACE, extensions);
this.condition = Objects.requireNonNull(condition, "condition must not be null");
// Some implementations may send the condition as non-empty element containing the empty string, that is
// <condition xmlns='foo'></condition>, 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;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("XMPPError: ");
sb.append(condition.toString()).append(" - ").append(type.toString());
String descriptiveText = getDescriptiveText();
if (descriptiveText != null) {
sb.append(" [").append(descriptiveText).append(']');
}
if (errorGenerator != null) {
sb.append(". Generated by ").append(errorGenerator);
}
return sb.toString();
}
@Override
public String getElementName() {
return QNAME.getLocalPart();
}
@Override
public String getNamespace() {
return QNAME.getNamespaceURI();
}
@Override
public XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.attribute("type", type.toString());
xml.optAttribute("by", errorGenerator);
xml.rightAngleBracket();
xml.halfOpenElement(condition.toString());
xml.xmlnsAttribute(ERROR_CONDITION_AND_TEXT_NAMESPACE);
if (conditionText != null) {
xml.rightAngleBracket();
xml.escape(conditionText);
xml.closeElement(condition.toString());
}
else {
xml.closeEmptyElement();
}
addDescriptiveTextsAndExtensions(xml);
xml.closeElement(this);
return xml;
}
public static StanzaError.Builder from(Condition condition, String descriptiveText) {
StanzaError.Builder builder = getBuilder().setCondition(condition);
if (descriptiveText != null) {
Map<String, String> descriptiveTexts = new HashMap<>();
descriptiveTexts.put("en", descriptiveText);
builder.setDescriptiveTexts(descriptiveTexts);
}
return builder;
}
public static Builder getBuilder() {
return new Builder();
}
public static Builder getBuilder(Condition condition) {
return getBuilder().setCondition(condition);
}
public static Builder getBuilder(StanzaError xmppError) {
return getBuilder().copyFrom(xmppError);
}
public static final class Builder extends AbstractError.Builder<Builder> {
private Condition condition;
private String conditionText;
private String errorGenerator;
private Type type;
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 copyFrom(StanzaError xmppError) {
setCondition(xmppError.getCondition());
setType(xmppError.getType());
setConditionText(xmppError.getConditionText());
setErrorGenerator(xmppError.getErrorGenerator());
setDescriptiveTexts(xmppError.descriptiveTexts);
setTextNamespace(xmppError.textNamespace);
setExtensions(xmppError.extensions);
return this;
}
public StanzaError build() {
return new StanzaError(condition, conditionText, errorGenerator, type, descriptiveTexts,
extensions);
}
@Override
protected Builder getThis() {
return this;
}
}
/**
* 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 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) {
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;
}
}
}