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.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025 026import org.jivesoftware.smack.util.StringUtils; 027 028/** 029 * Represents a XMPP error sub-packet. Typically, a server responds to a request that has 030 * problems by sending the 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</b></td><td><b>Type</b></td></hr> 035 * <tr><td>internal-server-error</td><td>WAIT</td></tr> 036 * <tr><td>forbidden</td><td>AUTH</td></tr> 037 * <tr><td>bad-request</td><td>MODIFY</td></tr> 038 * <tr><td>item-not-found</td><td>CANCEL</td></tr> 039 * <tr><td>conflict</td><td>CANCEL</td></tr> 040 * <tr><td>feature-not-implemented</td><td>CANCEL</td></tr> 041 * <tr><td>gone</td><td>MODIFY</td></tr> 042 * <tr><td>jid-malformed</td><td>MODIFY</td></tr> 043 * <tr><td>not-acceptable</td><td> MODIFY</td></tr> 044 * <tr><td>not-allowed</td><td>CANCEL</td></tr> 045 * <tr><td>not-authorized</td><td>AUTH</td></tr> 046 * <tr><td>payment-required</td><td>AUTH</td></tr> 047 * <tr><td>recipient-unavailable</td><td>WAIT</td></tr> 048 * <tr><td>redirect</td><td>MODIFY</td></tr> 049 * <tr><td>registration-required</td><td>AUTH</td></tr> 050 * <tr><td>remote-server-not-found</td><td>CANCEL</td></tr> 051 * <tr><td>remote-server-timeout</td><td>WAIT</td></tr> 052 * <tr><td>remote-server-error</td><td>CANCEL</td></tr> 053 * <tr><td>resource-constraint</td><td>WAIT</td></tr> 054 * <tr><td>service-unavailable</td><td>CANCEL</td></tr> 055 * <tr><td>subscription-required</td><td>AUTH</td></tr> 056 * <tr><td>undefined-condition</td><td>WAIT</td></tr> 057 * <tr><td>unexpected-condition</td><td>WAIT</td></tr> 058 * <tr><td>request-timeout</td><td>CANCEL</td></tr> 059 * </table> 060 * 061 * @author Matt Tucker 062 * @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> 063 */ 064public class XMPPError { 065 066 private final Type type; 067 private final String condition; 068 private String message; 069 private List<PacketExtension> applicationExtensions = null; 070 071 /** 072 * Creates a new error with the specified condition inferring the type. 073 * If the Condition is predefined, client code should be like: 074 * new XMPPError(XMPPError.Condition.remote_server_timeout); 075 * If the Condition is not predefined, invocations should be like 076 * new XMPPError(new XMPPError.Condition("my_own_error")); 077 * 078 * @param condition the error condition. 079 */ 080 public XMPPError(Condition condition) { 081 // Look for the condition and its default type 082 ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition); 083 this.condition = condition.value; 084 if (defaultErrorSpecification != null) { 085 // If there is a default error specification for the received condition, 086 // it get configured with the inferred type. 087 type = defaultErrorSpecification.getType(); 088 } else { 089 type = null; 090 } 091 } 092 093 /** 094 * Creates a new error with the specified condition and message infering the type. 095 * If the Condition is predefined, client code should be like: 096 * new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation"); 097 * If the Condition is not predefined, invocations should be like 098 * new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation"); 099 * 100 * @param condition the error condition. 101 * @param messageText a message describing the error. 102 */ 103 public XMPPError(Condition condition, String messageText) { 104 this(condition); 105 this.message = messageText; 106 } 107 108 /** 109 * Creates a new error with the specified type, condition and message. 110 * This constructor is used when the condition is not recognized automatically by XMPPError 111 * i.e. there is not a defined instance of ErrorCondition or it does not apply the default 112 * specification. 113 * 114 * @param type the error type. 115 * @param condition the error condition. 116 * @param message a message describing the error. 117 * @param extension list of packet extensions 118 */ 119 public XMPPError(Type type, String condition, String message, 120 List<PacketExtension> extension) { 121 this.type = type; 122 this.condition = condition; 123 this.message = message; 124 this.applicationExtensions = extension; 125 } 126 127 /** 128 * Returns the error condition. 129 * 130 * @return the error condition. 131 */ 132 public String getCondition() { 133 return condition; 134 } 135 136 /** 137 * Returns the error type. 138 * 139 * @return the error type. 140 */ 141 public Type getType() { 142 return type; 143 } 144 145 /** 146 * Returns the message describing the error, or null if there is no message. 147 * 148 * @return the message describing the error, or null if there is no message. 149 */ 150 public String getMessage() { 151 return message; 152 } 153 154 /** 155 * Returns the error as XML. 156 * 157 * @return the error as XML. 158 */ 159 public CharSequence toXML() { 160 StringBuilder buf = new StringBuilder(); 161 buf.append("<error"); 162 if (type != null) { 163 buf.append(" type=\""); 164 buf.append(type.name().toLowerCase(Locale.US)); 165 buf.append("\""); 166 } 167 buf.append(">"); 168 if (condition != null) { 169 buf.append("<").append(condition); 170 buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); 171 } 172 if (message != null) { 173 buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"); 174 buf.append(message); 175 buf.append("</text>"); 176 } 177 for (PacketExtension element : this.getExtensions()) { 178 buf.append(element.toXML()); 179 } 180 buf.append("</error>"); 181 return buf.toString(); 182 } 183 184 public String toString() { 185 StringBuilder txt = new StringBuilder(); 186 if (condition != null) { 187 txt.append(condition); 188 } 189 if (message != null) { 190 txt.append(" ").append(message); 191 } 192 return txt.toString(); 193 } 194 195 /** 196 * Returns a List of the error extensions attached to the xmppError. 197 * An application MAY provide application-specific error information by including a 198 * properly-namespaced child in the error element. 199 * 200 * @return a List of the error extensions. 201 */ 202 public synchronized List<PacketExtension> getExtensions() { 203 if (applicationExtensions == null) { 204 return Collections.emptyList(); 205 } 206 return Collections.unmodifiableList(applicationExtensions); 207 } 208 209 /** 210 * Returns the first packet extension that matches the specified element name and 211 * namespace, or <tt>null</tt> if it doesn't exist. 212 * 213 * @param elementName the XML element name of the packet extension. 214 * @param namespace the XML element namespace of the packet extension. 215 * @return the extension, or <tt>null</tt> if it doesn't exist. 216 */ 217 public synchronized PacketExtension getExtension(String elementName, String namespace) { 218 if (applicationExtensions == null || elementName == null || namespace == null) { 219 return null; 220 } 221 for (PacketExtension ext : applicationExtensions) { 222 if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { 223 return ext; 224 } 225 } 226 return null; 227 } 228 229 /** 230 * Adds a packet extension to the error. 231 * 232 * @param extension a packet extension. 233 */ 234 public synchronized void addExtension(PacketExtension extension) { 235 if (applicationExtensions == null) { 236 applicationExtensions = new ArrayList<PacketExtension>(); 237 } 238 applicationExtensions.add(extension); 239 } 240 241 /** 242 * Set the packet extension to the error. 243 * 244 * @param extension a packet extension. 245 */ 246 public synchronized void setExtension(List<PacketExtension> extension) { 247 applicationExtensions = extension; 248 } 249 250 /** 251 * A class to represent the type of the Error. The types are: 252 * 253 * <ul> 254 * <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary) 255 * <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) 256 * <li>XMPPError.Type.MODIFY - retry after changing the data sent 257 * <li>XMPPError.Type.AUTH - retry after providing credentials 258 * <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning) 259 * </ul> 260 */ 261 public static enum Type { 262 WAIT, 263 CANCEL, 264 MODIFY, 265 AUTH, 266 CONTINUE 267 } 268 269 /** 270 * A class to represent predefined error conditions. 271 */ 272 public static class Condition implements CharSequence { 273 274 public static final Condition internal_server_error = new Condition("internal-server-error"); 275 public static final Condition forbidden = new Condition("forbidden"); 276 public static final Condition bad_request = new Condition("bad-request"); 277 public static final Condition conflict = new Condition("conflict"); 278 public static final Condition feature_not_implemented = new Condition("feature-not-implemented"); 279 public static final Condition gone = new Condition("gone"); 280 public static final Condition item_not_found = new Condition("item-not-found"); 281 public static final Condition jid_malformed = new Condition("jid-malformed"); 282 public static final Condition not_acceptable = new Condition("not-acceptable"); 283 public static final Condition not_allowed = new Condition("not-allowed"); 284 public static final Condition not_authorized = new Condition("not-authorized"); 285 public static final Condition payment_required = new Condition("payment-required"); 286 public static final Condition recipient_unavailable = new Condition("recipient-unavailable"); 287 public static final Condition redirect = new Condition("redirect"); 288 public static final Condition registration_required = new Condition("registration-required"); 289 public static final Condition remote_server_error = new Condition("remote-server-error"); 290 public static final Condition remote_server_not_found = new Condition("remote-server-not-found"); 291 public static final Condition remote_server_timeout = new Condition("remote-server-timeout"); 292 public static final Condition resource_constraint = new Condition("resource-constraint"); 293 public static final Condition service_unavailable = new Condition("service-unavailable"); 294 public static final Condition subscription_required = new Condition("subscription-required"); 295 public static final Condition undefined_condition = new Condition("undefined-condition"); 296 public static final Condition unexpected_request = new Condition("unexpected-request"); 297 public static final Condition request_timeout = new Condition("request-timeout"); 298 299 private final String value; 300 301 public Condition(String value) { 302 this.value = value; 303 } 304 305 @Override 306 public String toString() { 307 return value; 308 } 309 310 @Override 311 public boolean equals(Object other) { 312 if (other == null) { 313 return false; 314 } 315 return toString().equals(other.toString()); 316 } 317 318 public boolean equals(CharSequence other) { 319 return StringUtils.nullSafeCharSequenceEquals(this, other); 320 } 321 322 @Override 323 public int hashCode() { 324 return value.hashCode(); 325 } 326 327 @Override 328 public int length() { 329 return value.length(); 330 } 331 332 @Override 333 public char charAt(int index) { 334 return value.charAt(index); 335 } 336 337 @Override 338 public CharSequence subSequence(int start, int end) { 339 return value.subSequence(start, end); 340 } 341 } 342 343 344 /** 345 * A class to represent the error specification used to infer common usage. 346 */ 347 private static class ErrorSpecification { 348 private static Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>(); 349 350 private final Type type; 351 @SuppressWarnings("unused") 352 private final Condition condition; 353 354 private ErrorSpecification(Condition condition, Type type) { 355 this.type = type; 356 this.condition = condition; 357 } 358 359 static { 360 instances.put(Condition.internal_server_error, new ErrorSpecification( 361 Condition.internal_server_error, Type.WAIT)); 362 instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden, 363 Type.AUTH)); 364 instances.put(Condition.bad_request, new XMPPError.ErrorSpecification( 365 Condition.bad_request, Type.MODIFY)); 366 instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification( 367 Condition.item_not_found, Type.CANCEL)); 368 instances.put(Condition.conflict, new XMPPError.ErrorSpecification( 369 Condition.conflict, Type.CANCEL)); 370 instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification( 371 Condition.feature_not_implemented, Type.CANCEL)); 372 instances.put(Condition.gone, new XMPPError.ErrorSpecification( 373 Condition.gone, Type.MODIFY)); 374 instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification( 375 Condition.jid_malformed, Type.MODIFY)); 376 instances.put(Condition.not_acceptable, new XMPPError.ErrorSpecification( 377 Condition.not_acceptable, Type.MODIFY)); 378 instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification( 379 Condition.not_allowed, Type.CANCEL)); 380 instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification( 381 Condition.not_authorized, Type.AUTH)); 382 instances.put(Condition.payment_required, new XMPPError.ErrorSpecification( 383 Condition.payment_required, Type.AUTH)); 384 instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification( 385 Condition.recipient_unavailable, Type.WAIT)); 386 instances.put(Condition.redirect, new XMPPError.ErrorSpecification( 387 Condition.redirect, Type.MODIFY)); 388 instances.put(Condition.registration_required, new XMPPError.ErrorSpecification( 389 Condition.registration_required, Type.AUTH)); 390 instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification( 391 Condition.remote_server_not_found, Type.CANCEL)); 392 instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification( 393 Condition.remote_server_timeout, Type.WAIT)); 394 instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification( 395 Condition.remote_server_error, Type.CANCEL)); 396 instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification( 397 Condition.resource_constraint, Type.WAIT)); 398 instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification( 399 Condition.service_unavailable, Type.CANCEL)); 400 instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification( 401 Condition.subscription_required, Type.AUTH)); 402 instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification( 403 Condition.undefined_condition, Type.WAIT)); 404 instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification( 405 Condition.unexpected_request, Type.WAIT)); 406 instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification( 407 Condition.request_timeout, Type.CANCEL)); 408 } 409 410 protected static ErrorSpecification specFor(Condition condition) { 411 return instances.get(condition); 412 } 413 414 /** 415 * Returns the error type. 416 * 417 * @return the error type. 418 */ 419 protected Type getType() { 420 return type; 421 } 422 } 423}