001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.packet; 018 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.logging.Logger; 024 025import org.jivesoftware.smack.util.StringUtils; 026import org.jivesoftware.smack.util.XmlStringBuilder; 027 028/** 029 * Represents an XMPP error sub-packet. Typically, a server responds to a request that has 030 * problems by sending the stanza(/packet) back and including an error packet. Each error has a type, 031 * error condition as well as as an optional text explanation. Typical errors are:<p> 032 * 033 * <table border=1> 034 * <hr><td><b>XMPP Error Condition</b></td><td><b>Type</b></td><td><b>RFC 6120 Section</b></td></hr> 035 * <tr><td>bad-request</td><td>MODIFY</td><td>8.3.3.1</td></tr> 036 * <tr><td>conflict</td><td>CANCEL</td><td>8.3.3.2</td></tr> 037 * <tr><td>feature-not-implemented</td><td>CANCEL</td><td>8.3.3.3</td></tr> 038 * <tr><td>forbidden</td><td>AUTH</td><td>8.3.3.4</td></tr> 039 * <tr><td>gone</td><td>MODIFY</td><td>8.3.3.5</td></tr> 040 * <tr><td>internal-server-error</td><td>WAIT</td><td>8.3.3.6</td></tr> 041 * <tr><td>item-not-found</td><td>CANCEL</td><td>8.3.3.7</td></tr> 042 * <tr><td>jid-malformed</td><td>MODIFY</td><td>8.3.3.8</td></tr> 043 * <tr><td>not-acceptable</td><td> MODIFY</td><td>8.3.3.9</td></tr> 044 * <tr><td>not-allowed</td><td>CANCEL</td><td>8.3.3.10</td></tr> 045 * <tr><td>not-authorized</td><td>AUTH</td><td>8.3.3.11</td></tr> 046 * <tr><td>policy-violation</td><td>AUTH</td><td>8.3.3.12</td></tr> 047 * <tr><td>recipient-unavailable</td><td>WAIT</td><td>8.3.3.13</td></tr> 048 * <tr><td>redirect</td><td>MODIFY</td><td>8.3.3.14</td></tr> 049 * <tr><td>registration-required</td><td>AUTH</td><td>8.3.3.15</td></tr> 050 * <tr><td>remote-server-not-found</td><td>CANCEL</td><td>8.3.3.16</td></tr> 051 * <tr><td>remote-server-timeout</td><td>WAIT</td><td>8.3.3.17</td></tr> 052 * <tr><td>resource-constraint</td><td>WAIT</td><td>8.3.3.18</td></tr> 053 * <tr><td>service-unavailable</td><td>CANCEL</td><td>8.3.3.19</td></tr> 054 * <tr><td>subscription-required</td><td>AUTH</td><td>8.3.3.20</td></tr> 055 * <tr><td>undefined-condition</td><td>WAIT</td><td>8.3.3.21</td></tr> 056 * <tr><td>unexpected-request</td><td>WAIT</td><td>8.3.3.22</td></tr> 057 * </table> 058 * 059 * @author Matt Tucker 060 * @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> 061 */ 062public class XMPPError extends AbstractError { 063 064 public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas"; 065 public static final String ERROR = "error"; 066 067 private static final Logger LOGGER = Logger.getLogger(XMPPError.class.getName()); 068 private static final Map<Condition, Type> CONDITION_TO_TYPE = new HashMap<Condition, Type>(); 069 070 static { 071 CONDITION_TO_TYPE.put(Condition.bad_request, Type.MODIFY); 072 CONDITION_TO_TYPE.put(Condition.conflict, Type.CANCEL); 073 CONDITION_TO_TYPE.put(Condition.feature_not_implemented, Type.CANCEL); 074 CONDITION_TO_TYPE.put(Condition.forbidden, Type.AUTH); 075 CONDITION_TO_TYPE.put(Condition.gone, Type.CANCEL); 076 CONDITION_TO_TYPE.put(Condition.internal_server_error, Type.CANCEL); 077 CONDITION_TO_TYPE.put(Condition.item_not_found, Type.CANCEL); 078 CONDITION_TO_TYPE.put(Condition.jid_malformed, Type.MODIFY); 079 CONDITION_TO_TYPE.put(Condition.not_acceptable, Type.MODIFY); 080 CONDITION_TO_TYPE.put(Condition.not_allowed, Type.CANCEL); 081 CONDITION_TO_TYPE.put(Condition.not_authorized, Type.AUTH); 082 CONDITION_TO_TYPE.put(Condition.policy_violation, Type.MODIFY); 083 CONDITION_TO_TYPE.put(Condition.recipient_unavailable, Type.WAIT); 084 CONDITION_TO_TYPE.put(Condition.redirect, Type.MODIFY); 085 CONDITION_TO_TYPE.put(Condition.registration_required, Type.AUTH); 086 CONDITION_TO_TYPE.put(Condition.remote_server_not_found, Type.CANCEL); 087 CONDITION_TO_TYPE.put(Condition.remote_server_timeout, Type.WAIT); 088 CONDITION_TO_TYPE.put(Condition.resource_constraint, Type.WAIT); 089 CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.WAIT); 090 CONDITION_TO_TYPE.put(Condition.subscription_required, Type.WAIT); 091 CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.MODIFY); 092 } 093 private final Condition condition; 094 private final String conditionText; 095 private final String errorGenerator; 096 private final Type type; 097 098 public XMPPError(Condition condition) { 099 this(condition, null, null, null, null, null); 100 } 101 102 public XMPPError(Condition condition, ExtensionElement applicationSpecificCondition) { 103 this(condition, null, null, null, null, Arrays.asList(applicationSpecificCondition)); 104 } 105 106 /** 107 * Creates a new error with the specified type, condition and message. 108 * This constructor is used when the condition is not recognized automatically by XMPPError 109 * i.e. there is not a defined instance of ErrorCondition or it does not apply the default 110 * specification. 111 * 112 * @param type the error type. 113 * @param condition the error condition. 114 * @param descriptiveTexts 115 * @param extensions list of stanza(/packet) extensions 116 */ 117 public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map<String, String> descriptiveTexts, 118 List<ExtensionElement> extensions) { 119 super(descriptiveTexts, NAMESPACE, extensions); 120 this.condition = condition; 121 // Some implementations may send the condition as non-empty element containing the empty string, that is 122 // <condition xmlns='foo'></condition>, in this case the parser may calls this constructor with the empty string 123 // as conditionText, therefore reset it to null if it's the empty string 124 if (StringUtils.isNullOrEmpty(conditionText)) { 125 conditionText = null; 126 } 127 if (conditionText != null) { 128 switch (condition) { 129 case gone: 130 case redirect: 131 break; 132 default: 133 throw new IllegalArgumentException( 134 "Condition text can only be set with condtion types 'gone' and 'redirect', not " 135 + condition); 136 } 137 } 138 this.conditionText = conditionText; 139 this.errorGenerator = errorGenerator; 140 if (type == null) { 141 Type determinedType = CONDITION_TO_TYPE.get(condition); 142 if (determinedType == null) { 143 LOGGER.warning("Could not determine type for condition: " + condition); 144 determinedType = Type.CANCEL; 145 } 146 this.type = determinedType; 147 } else { 148 this.type = type; 149 } 150 } 151 152 /** 153 * Returns the error condition. 154 * 155 * @return the error condition. 156 */ 157 public Condition getCondition() { 158 return condition; 159 } 160 161 /** 162 * Returns the error type. 163 * 164 * @return the error type. 165 */ 166 public Type getType() { 167 return type; 168 } 169 170 public String getErrorGenerator() { 171 return errorGenerator; 172 } 173 174 public String getConditionText() { 175 return conditionText; 176 } 177 178 @Override 179 public String toString() { 180 StringBuilder sb = new StringBuilder("XMPPError: "); 181 sb.append(condition.toString()).append(" - ").append(type.toString()); 182 if (errorGenerator != null) { 183 sb.append(". Generated by ").append(errorGenerator); 184 } 185 return sb.toString(); 186 } 187 188 /** 189 * Returns the error as XML. 190 * 191 * @return the error as XML. 192 */ 193 public XmlStringBuilder toXML() { 194 XmlStringBuilder xml = new XmlStringBuilder(); 195 xml.halfOpenElement(ERROR); 196 xml.attribute("type", type.toString()); 197 xml.optAttribute("by", errorGenerator); 198 xml.rightAngleBracket(); 199 200 xml.halfOpenElement(condition.toString()); 201 xml.xmlnsAttribute(NAMESPACE); 202 if (conditionText != null) { 203 xml.rightAngleBracket(); 204 xml.escape(conditionText); 205 xml.closeElement(condition.toString()); 206 } 207 else { 208 xml.closeEmptyElement(); 209 } 210 211 addDescriptiveTextsAndExtensions(xml); 212 213 xml.closeElement(ERROR); 214 return xml; 215 } 216 217 public static XMPPError from(Condition condition, String descriptiveText) { 218 Map<String, String> descriptiveTexts = new HashMap<String, String>(); 219 descriptiveTexts.put("en", descriptiveText); 220 return new XMPPError(condition, null, null, null, descriptiveTexts, null); 221 } 222 223 /** 224 * A class to represent the type of the Error. The types are: 225 * 226 * <ul> 227 * <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary) 228 * <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) 229 * <li>XMPPError.Type.MODIFY - retry after changing the data sent 230 * <li>XMPPError.Type.AUTH - retry after providing credentials 231 * <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning) 232 * </ul> 233 */ 234 public static enum Type { 235 WAIT, 236 CANCEL, 237 MODIFY, 238 AUTH, 239 CONTINUE; 240 241 @Override 242 public String toString() { 243 // Locale.US not required, since Type consists only of ASCII chars 244 return name().toLowerCase(); 245 } 246 247 public static Type fromString(String string) { 248 // Locale.US not required, since Type consists only of ASCII chars 249 string = string.toUpperCase(); 250 return Type.valueOf(string); 251 } 252 } 253 254 public enum Condition { 255 bad_request, 256 conflict, 257 feature_not_implemented, 258 forbidden, 259 gone, 260 internal_server_error, 261 item_not_found, 262 jid_malformed, 263 not_acceptable, 264 not_allowed, 265 not_authorized, 266 policy_violation, 267 recipient_unavailable, 268 redirect, 269 registration_required, 270 remote_server_not_found, 271 remote_server_timeout, 272 resource_constraint, 273 service_unavailable, 274 subscription_required, 275 undefined_condition, 276 unexpected_request; 277 278 @Override 279 public String toString() { 280 return this.name().replace('_', '-'); 281 } 282 283 public static Condition fromString(String string) { 284 // Backwards compatibility for older implementations still using RFC 3920. RFC 6120 285 // changed 'xml-not-well-formed' to 'not-well-formed'. 286 if ("xml-not-well-formed".equals(string)) { 287 string = "not-well-formed"; 288 } 289 string = string.replace('-', '_'); 290 Condition condition = null; 291 try { 292 condition = Condition.valueOf(string); 293 } catch (Exception e) { 294 throw new IllegalStateException("Could not transform string '" + string + "' to XMPPErrorCondition", e); 295 } 296 return condition; 297 } 298 } 299 300}