XMPPError.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.packet;

  18. import java.util.Arrays;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.logging.Logger;

  23. import org.jivesoftware.smack.util.StringUtils;
  24. import org.jivesoftware.smack.util.XmlStringBuilder;

  25. /**
  26.  * Represents an XMPP error sub-packet. Typically, a server responds to a request that has
  27.  * problems by sending the packet back and including an error packet. Each error has a type,
  28.  * error condition as well as as an optional text explanation. Typical errors are:<p>
  29.  *
  30.  * <table border=1>
  31.  *      <hr><td><b>XMPP Error Condition</b></td><td><b>Type</b></td><td><b>RFC 6120 Section</b></td></hr>
  32.  *      <tr><td>bad-request</td><td>MODIFY</td><td>8.3.3.1</td></tr>
  33.  *      <tr><td>conflict</td><td>CANCEL</td><td>8.3.3.2</td></tr>
  34.  *      <tr><td>feature-not-implemented</td><td>CANCEL</td><td>8.3.3.3</td></tr>
  35.  *      <tr><td>forbidden</td><td>AUTH</td><td>8.3.3.4</td></tr>
  36.  *      <tr><td>gone</td><td>MODIFY</td><td>8.3.3.5</td></tr>
  37.  *      <tr><td>internal-server-error</td><td>WAIT</td><td>8.3.3.6</td></tr>
  38.  *      <tr><td>item-not-found</td><td>CANCEL</td><td>8.3.3.7</td></tr>
  39.  *      <tr><td>jid-malformed</td><td>MODIFY</td><td>8.3.3.8</td></tr>
  40.  *      <tr><td>not-acceptable</td><td> MODIFY</td><td>8.3.3.9</td></tr>
  41.  *      <tr><td>not-allowed</td><td>CANCEL</td><td>8.3.3.10</td></tr>
  42.  *      <tr><td>not-authorized</td><td>AUTH</td><td>8.3.3.11</td></tr>
  43.  *      <tr><td>policy-violation</td><td>AUTH</td><td>8.3.3.12</td></tr>
  44.  *      <tr><td>recipient-unavailable</td><td>WAIT</td><td>8.3.3.13</td></tr>
  45.  *      <tr><td>redirect</td><td>MODIFY</td><td>8.3.3.14</td></tr>
  46.  *      <tr><td>registration-required</td><td>AUTH</td><td>8.3.3.15</td></tr>
  47.  *      <tr><td>remote-server-not-found</td><td>CANCEL</td><td>8.3.3.16</td></tr>
  48.  *      <tr><td>remote-server-timeout</td><td>WAIT</td><td>8.3.3.17</td></tr>
  49.  *      <tr><td>resource-constraint</td><td>WAIT</td><td>8.3.3.18</td></tr>
  50.  *      <tr><td>service-unavailable</td><td>CANCEL</td><td>8.3.3.19</td></tr>
  51.  *      <tr><td>subscription-required</td><td>AUTH</td><td>8.3.3.20</td></tr>
  52.  *      <tr><td>undefined-condition</td><td>WAIT</td><td>8.3.3.21</td></tr>
  53.  *      <tr><td>unexpected-request</td><td>WAIT</td><td>8.3.3.22</td></tr>
  54.  * </table>
  55.  *
  56.  * @author Matt Tucker
  57.  * @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>
  58.  */
  59. public class XMPPError extends AbstractError {

  60.     public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
  61.     public static final String ERROR = "error";

  62.     private static final Logger LOGGER = Logger.getLogger(XMPPError.class.getName());
  63.     private static final Map<Condition, Type> CONDITION_TO_TYPE = new HashMap<Condition, Type>();

  64.     static {
  65.         CONDITION_TO_TYPE.put(Condition.bad_request, Type.MODIFY);
  66.         CONDITION_TO_TYPE.put(Condition.conflict, Type.CANCEL);
  67.         CONDITION_TO_TYPE.put(Condition.feature_not_implemented, Type.CANCEL);
  68.         CONDITION_TO_TYPE.put(Condition.forbidden, Type.AUTH);
  69.         CONDITION_TO_TYPE.put(Condition.gone, Type.CANCEL);
  70.         CONDITION_TO_TYPE.put(Condition.internal_server_error, Type.CANCEL);
  71.         CONDITION_TO_TYPE.put(Condition.item_not_found, Type.CANCEL);
  72.         CONDITION_TO_TYPE.put(Condition.jid_malformed, Type.MODIFY);
  73.         CONDITION_TO_TYPE.put(Condition.not_acceptable, Type.MODIFY);
  74.         CONDITION_TO_TYPE.put(Condition.not_allowed, Type.CANCEL);
  75.         CONDITION_TO_TYPE.put(Condition.not_authorized, Type.AUTH);
  76.         CONDITION_TO_TYPE.put(Condition.policy_violation, Type.MODIFY);
  77.         CONDITION_TO_TYPE.put(Condition.recipient_unavailable, Type.WAIT);
  78.         CONDITION_TO_TYPE.put(Condition.redirect, Type.MODIFY);
  79.         CONDITION_TO_TYPE.put(Condition.registration_required, Type.AUTH);
  80.         CONDITION_TO_TYPE.put(Condition.remote_server_not_found, Type.CANCEL);
  81.         CONDITION_TO_TYPE.put(Condition.remote_server_timeout, Type.WAIT);
  82.         CONDITION_TO_TYPE.put(Condition.resource_constraint, Type.WAIT);
  83.         CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.WAIT);
  84.         CONDITION_TO_TYPE.put(Condition.subscription_required, Type.WAIT);
  85.         CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.MODIFY);
  86.     }
  87.     private final Condition condition;
  88.     private final String conditionText;
  89.     private final String errorGenerator;
  90.     private final Type type;

  91.     public XMPPError(Condition condition) {
  92.         this(condition, null, null, null, null, null);
  93.     }

  94.     public XMPPError(Condition condition, ExtensionElement applicationSpecificCondition) {
  95.         this(condition, null, null, null, null, Arrays.asList(applicationSpecificCondition));
  96.     }

  97.     /**
  98.      * Creates a new error with the specified type, condition and message.
  99.      * This constructor is used when the condition is not recognized automatically by XMPPError
  100.      * i.e. there is not a defined instance of ErrorCondition or it does not apply the default
  101.      * specification.
  102.      *
  103.      * @param type the error type.
  104.      * @param condition the error condition.
  105.      * @param descriptiveTexts
  106.      * @param extensions list of packet extensions
  107.      */
  108.     public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map<String, String> descriptiveTexts,
  109.             List<ExtensionElement> extensions) {
  110.         super(descriptiveTexts, NAMESPACE, extensions);
  111.         this.condition = condition;
  112.         // Some implementations may send the condition as non-empty element containing the empty string, that is
  113.         // <condition xmlns='foo'></condition>, in this case the parser may calls this constructor with the empty string
  114.         // as conditionText, therefore reset it to null if it's the empty string
  115.         if (StringUtils.isNullOrEmpty(conditionText)) {
  116.             conditionText = null;
  117.         }
  118.         if (conditionText != null) {
  119.             switch (condition) {
  120.             case gone:
  121.             case redirect:
  122.                 break;
  123.             default:
  124.                 throw new IllegalArgumentException(
  125.                                 "Condition text can only be set with condtion types 'gone' and 'redirect', not "
  126.                                                 + condition);
  127.             }
  128.         }
  129.         this.conditionText = conditionText;
  130.         this.errorGenerator = errorGenerator;
  131.         if (type == null) {
  132.             Type determinedType = CONDITION_TO_TYPE.get(condition);
  133.             if (determinedType == null) {
  134.                 LOGGER.warning("Could not determine type for condition: " + condition);
  135.                 determinedType = Type.CANCEL;
  136.             }
  137.             this.type = determinedType;
  138.         } else {
  139.             this.type = type;
  140.         }
  141.     }

  142.     /**
  143.      * Returns the error condition.
  144.      *
  145.      * @return the error condition.
  146.      */
  147.     public Condition getCondition() {
  148.         return condition;
  149.     }

  150.     /**
  151.      * Returns the error type.
  152.      *
  153.      * @return the error type.
  154.      */
  155.     public Type getType() {
  156.         return type;
  157.     }

  158.     public String getErrorGenerator() {
  159.         return errorGenerator;
  160.     }

  161.     public String getConditionText() {
  162.         return conditionText;
  163.     }

  164.     @Override
  165.     public String toString() {
  166.         StringBuilder sb = new StringBuilder("XMPPError: ");
  167.         sb.append(condition.toString()).append(" - ").append(type.toString());
  168.         if (errorGenerator != null) {
  169.             sb.append(". Generated by ").append(errorGenerator);
  170.         }
  171.         return sb.toString();
  172.     }

  173.     /**
  174.      * Returns the error as XML.
  175.      *
  176.      * @return the error as XML.
  177.      */
  178.     public XmlStringBuilder toXML() {
  179.         XmlStringBuilder xml = new XmlStringBuilder();
  180.         xml.halfOpenElement(ERROR);
  181.         xml.attribute("type", type.toString());
  182.         xml.optAttribute("by", errorGenerator);
  183.         xml.rightAngleBracket();

  184.         xml.halfOpenElement(condition.toString());
  185.         xml.xmlnsAttribute(NAMESPACE);
  186.         if (conditionText != null) {
  187.             xml.rightAngleBracket();
  188.             xml.escape(conditionText);
  189.             xml.closeElement(condition.toString());
  190.         }
  191.         else {
  192.             xml.closeEmptyElement();
  193.         }

  194.         addDescriptiveTextsAndExtensions(xml);

  195.         xml.closeElement(ERROR);
  196.         return xml;
  197.     }

  198.     public static XMPPError from(Condition condition, String descriptiveText) {
  199.         Map<String, String> descriptiveTexts = new HashMap<String, String>();
  200.         descriptiveTexts.put("en", descriptiveText);
  201.         return new XMPPError(condition, null, null, null, descriptiveTexts, null);
  202.     }

  203.     /**
  204.      * A class to represent the type of the Error. The types are:
  205.      *
  206.      * <ul>
  207.      *      <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary)
  208.      *      <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
  209.      *      <li>XMPPError.Type.MODIFY - retry after changing the data sent
  210.      *      <li>XMPPError.Type.AUTH - retry after providing credentials
  211.      *      <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
  212.      * </ul>
  213.      */
  214.     public static enum Type {
  215.         WAIT,
  216.         CANCEL,
  217.         MODIFY,
  218.         AUTH,
  219.         CONTINUE;

  220.         @Override
  221.         public String toString() {
  222.             // Locale.US not required, since Type consists only of ASCII chars
  223.             return name().toLowerCase();
  224.         }

  225.         public static Type fromString(String string) {
  226.             // Locale.US not required, since Type consists only of ASCII chars
  227.             string = string.toUpperCase();
  228.             return Type.valueOf(string);
  229.         }
  230.     }

  231.     public enum Condition {
  232.         bad_request,
  233.         conflict,
  234.         feature_not_implemented,
  235.         forbidden,
  236.         gone,
  237.         internal_server_error,
  238.         item_not_found,
  239.         jid_malformed,
  240.         not_acceptable,
  241.         not_allowed,
  242.         not_authorized,
  243.         policy_violation,
  244.         recipient_unavailable,
  245.         redirect,
  246.         registration_required,
  247.         remote_server_not_found,
  248.         remote_server_timeout,
  249.         resource_constraint,
  250.         service_unavailable,
  251.         subscription_required,
  252.         undefined_condition,
  253.         unexpected_request;

  254.         @Override
  255.         public String toString() {
  256.             return this.name().replace('_', '-');
  257.         }

  258.         public static Condition fromString(String string) {
  259.             // Backwards compatibility for older implementations still using RFC 3920. RFC 6120
  260.             // changed 'xml-not-well-formed' to 'not-well-formed'.
  261.             if ("xml-not-well-formed".equals(string)) {
  262.                 string = "not-well-formed";
  263.             }
  264.             string = string.replace('-', '_');
  265.             Condition condition = null;
  266.             try {
  267.                 condition = Condition.valueOf(string);
  268.             } catch (Exception e) {
  269.                 throw new IllegalStateException("Could not transform string '" + string + "' to XMPPErrorCondition", e);
  270.             }
  271.             return condition;
  272.         }
  273.     }

  274. }