Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 1 | /** |
| 2 | * $RCSfile$ |
| 3 | * $Revision$ |
| 4 | * $Date$ |
| 5 | * |
| 6 | * Copyright 2003-2007 Jive Software. |
| 7 | * |
| 8 | * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); |
| 9 | * you may not use this file except in compliance with the License. |
| 10 | * You may obtain a copy of the License at |
| 11 | * |
| 12 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 13 | * |
| 14 | * Unless required by applicable law or agreed to in writing, software |
| 15 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 17 | * See the License for the specific language governing permissions and |
| 18 | * limitations under the License. |
| 19 | */ |
| 20 | |
| 21 | package org.jivesoftware.smack.packet; |
| 22 | |
| 23 | import java.util.*; |
| 24 | |
| 25 | /** |
| 26 | * Represents a 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 code, 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>Code</b></td><td><b>XMPP Error</b></td><td><b>Type</b></td></hr> |
| 32 | * <tr><td>500</td><td>interna-server-error</td><td>WAIT</td></tr> |
| 33 | * <tr><td>403</td><td>forbidden</td><td>AUTH</td></tr> |
| 34 | * <tr><td>400</td<td>bad-request</td><td>MODIFY</td>></tr> |
| 35 | * <tr><td>404</td><td>item-not-found</td><td>CANCEL</td></tr> |
| 36 | * <tr><td>409</td><td>conflict</td><td>CANCEL</td></tr> |
| 37 | * <tr><td>501</td><td>feature-not-implemented</td><td>CANCEL</td></tr> |
| 38 | * <tr><td>302</td><td>gone</td><td>MODIFY</td></tr> |
| 39 | * <tr><td>400</td><td>jid-malformed</td><td>MODIFY</td></tr> |
| 40 | * <tr><td>406</td><td>no-acceptable</td><td> MODIFY</td></tr> |
| 41 | * <tr><td>405</td><td>not-allowed</td><td>CANCEL</td></tr> |
| 42 | * <tr><td>401</td><td>not-authorized</td><td>AUTH</td></tr> |
| 43 | * <tr><td>402</td><td>payment-required</td><td>AUTH</td></tr> |
| 44 | * <tr><td>404</td><td>recipient-unavailable</td><td>WAIT</td></tr> |
| 45 | * <tr><td>302</td><td>redirect</td><td>MODIFY</td></tr> |
| 46 | * <tr><td>407</td><td>registration-required</td><td>AUTH</td></tr> |
| 47 | * <tr><td>404</td><td>remote-server-not-found</td><td>CANCEL</td></tr> |
| 48 | * <tr><td>504</td><td>remote-server-timeout</td><td>WAIT</td></tr> |
| 49 | * <tr><td>502</td><td>remote-server-error</td><td>CANCEL</td></tr> |
| 50 | * <tr><td>500</td><td>resource-constraint</td><td>WAIT</td></tr> |
| 51 | * <tr><td>503</td><td>service-unavailable</td><td>CANCEL</td></tr> |
| 52 | * <tr><td>407</td><td>subscription-required</td><td>AUTH</td></tr> |
| 53 | * <tr><td>500</td><td>undefined-condition</td><td>WAIT</td></tr> |
| 54 | * <tr><td>400</td><td>unexpected-condition</td><td>WAIT</td></tr> |
| 55 | * <tr><td>408</td><td>request-timeout</td><td>CANCEL</td></tr> |
| 56 | * </table> |
| 57 | * |
| 58 | * @author Matt Tucker |
| 59 | */ |
| 60 | public class XMPPError { |
| 61 | |
| 62 | private int code; |
| 63 | private Type type; |
| 64 | private String condition; |
| 65 | private String message; |
| 66 | private List<PacketExtension> applicationExtensions = null; |
| 67 | |
| 68 | |
| 69 | /** |
| 70 | * Creates a new error with the specified condition infering the type and code. |
| 71 | * If the Condition is predefined, client code should be like: |
| 72 | * new XMPPError(XMPPError.Condition.remote_server_timeout); |
| 73 | * If the Condition is not predefined, invocations should be like |
| 74 | * new XMPPError(new XMPPError.Condition("my_own_error")); |
| 75 | * |
| 76 | * @param condition the error condition. |
| 77 | */ |
| 78 | public XMPPError(Condition condition) { |
| 79 | this.init(condition); |
| 80 | this.message = null; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Creates a new error with the specified condition and message infering the type and code. |
| 85 | * If the Condition is predefined, client code should be like: |
| 86 | * new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation"); |
| 87 | * If the Condition is not predefined, invocations should be like |
| 88 | * new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation"); |
| 89 | * |
| 90 | * @param condition the error condition. |
| 91 | * @param messageText a message describing the error. |
| 92 | */ |
| 93 | public XMPPError(Condition condition, String messageText) { |
| 94 | this.init(condition); |
| 95 | this.message = messageText; |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Creates a new error with the specified code and no message. |
| 100 | * |
| 101 | * @param code the error code. |
| 102 | * @deprecated new errors should be created using the constructor XMPPError(condition) |
| 103 | */ |
| 104 | public XMPPError(int code) { |
| 105 | this.code = code; |
| 106 | this.message = null; |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Creates a new error with the specified code and message. |
| 111 | * deprecated |
| 112 | * |
| 113 | * @param code the error code. |
| 114 | * @param message a message describing the error. |
| 115 | * @deprecated new errors should be created using the constructor XMPPError(condition, message) |
| 116 | */ |
| 117 | public XMPPError(int code, String message) { |
| 118 | this.code = code; |
| 119 | this.message = message; |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Creates a new error with the specified code, type, condition and message. |
| 124 | * This constructor is used when the condition is not recognized automatically by XMPPError |
| 125 | * i.e. there is not a defined instance of ErrorCondition or it does not applies the default |
| 126 | * specification. |
| 127 | * |
| 128 | * @param code the error code. |
| 129 | * @param type the error type. |
| 130 | * @param condition the error condition. |
| 131 | * @param message a message describing the error. |
| 132 | * @param extension list of packet extensions |
| 133 | */ |
| 134 | public XMPPError(int code, Type type, String condition, String message, |
| 135 | List<PacketExtension> extension) { |
| 136 | this.code = code; |
| 137 | this.type = type; |
| 138 | this.condition = condition; |
| 139 | this.message = message; |
| 140 | this.applicationExtensions = extension; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Initialize the error infering the type and code for the received condition. |
| 145 | * |
| 146 | * @param condition the error condition. |
| 147 | */ |
| 148 | private void init(Condition condition) { |
| 149 | // Look for the condition and its default code and type |
| 150 | ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition); |
| 151 | this.condition = condition.value; |
| 152 | if (defaultErrorSpecification != null) { |
| 153 | // If there is a default error specification for the received condition, |
| 154 | // it get configured with the infered type and code. |
| 155 | this.type = defaultErrorSpecification.getType(); |
| 156 | this.code = defaultErrorSpecification.getCode(); |
| 157 | } |
| 158 | } |
| 159 | /** |
| 160 | * Returns the error condition. |
| 161 | * |
| 162 | * @return the error condition. |
| 163 | */ |
| 164 | public String getCondition() { |
| 165 | return condition; |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Returns the error type. |
| 170 | * |
| 171 | * @return the error type. |
| 172 | */ |
| 173 | public Type getType() { |
| 174 | return type; |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Returns the error code. |
| 179 | * |
| 180 | * @return the error code. |
| 181 | */ |
| 182 | public int getCode() { |
| 183 | return code; |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Returns the message describing the error, or null if there is no message. |
| 188 | * |
| 189 | * @return the message describing the error, or null if there is no message. |
| 190 | */ |
| 191 | public String getMessage() { |
| 192 | return message; |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * Returns the error as XML. |
| 197 | * |
| 198 | * @return the error as XML. |
| 199 | */ |
| 200 | public String toXML() { |
| 201 | StringBuilder buf = new StringBuilder(); |
| 202 | buf.append("<error code=\"").append(code).append("\""); |
| 203 | if (type != null) { |
| 204 | buf.append(" type=\""); |
| 205 | buf.append(type.name()); |
| 206 | buf.append("\""); |
| 207 | } |
| 208 | buf.append(">"); |
| 209 | if (condition != null) { |
| 210 | buf.append("<").append(condition); |
| 211 | buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); |
| 212 | } |
| 213 | if (message != null) { |
| 214 | buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"); |
| 215 | buf.append(message); |
| 216 | buf.append("</text>"); |
| 217 | } |
| 218 | for (PacketExtension element : this.getExtensions()) { |
| 219 | buf.append(element.toXML()); |
| 220 | } |
| 221 | buf.append("</error>"); |
| 222 | return buf.toString(); |
| 223 | } |
| 224 | |
| 225 | public String toString() { |
| 226 | StringBuilder txt = new StringBuilder(); |
| 227 | if (condition != null) { |
| 228 | txt.append(condition); |
| 229 | } |
| 230 | txt.append("(").append(code).append(")"); |
| 231 | if (message != null) { |
| 232 | txt.append(" ").append(message); |
| 233 | } |
| 234 | return txt.toString(); |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Returns an Iterator for the error extensions attached to the xmppError. |
| 239 | * An application MAY provide application-specific error information by including a |
| 240 | * properly-namespaced child in the error element. |
| 241 | * |
| 242 | * @return an Iterator for the error extensions. |
| 243 | */ |
| 244 | public synchronized List<PacketExtension> getExtensions() { |
| 245 | if (applicationExtensions == null) { |
| 246 | return Collections.emptyList(); |
| 247 | } |
| 248 | return Collections.unmodifiableList(applicationExtensions); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Returns the first patcket extension that matches the specified element name and |
| 253 | * namespace, or <tt>null</tt> if it doesn't exist. |
| 254 | * |
| 255 | * @param elementName the XML element name of the packet extension. |
| 256 | * @param namespace the XML element namespace of the packet extension. |
| 257 | * @return the extension, or <tt>null</tt> if it doesn't exist. |
| 258 | */ |
| 259 | public synchronized PacketExtension getExtension(String elementName, String namespace) { |
| 260 | if (applicationExtensions == null || elementName == null || namespace == null) { |
| 261 | return null; |
| 262 | } |
| 263 | for (PacketExtension ext : applicationExtensions) { |
| 264 | if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { |
| 265 | return ext; |
| 266 | } |
| 267 | } |
| 268 | return null; |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * Adds a packet extension to the error. |
| 273 | * |
| 274 | * @param extension a packet extension. |
| 275 | */ |
| 276 | public synchronized void addExtension(PacketExtension extension) { |
| 277 | if (applicationExtensions == null) { |
| 278 | applicationExtensions = new ArrayList<PacketExtension>(); |
| 279 | } |
| 280 | applicationExtensions.add(extension); |
| 281 | } |
| 282 | |
| 283 | /** |
| 284 | * Set the packet extension to the error. |
| 285 | * |
| 286 | * @param extension a packet extension. |
| 287 | */ |
| 288 | public synchronized void setExtension(List<PacketExtension> extension) { |
| 289 | applicationExtensions = extension; |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * A class to represent the type of the Error. The types are: |
| 294 | * |
| 295 | * <ul> |
| 296 | * <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary) |
| 297 | * <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) |
| 298 | * <li>XMPPError.Type.MODIFY - retry after changing the data sent |
| 299 | * <li>XMPPError.Type.AUTH - retry after providing credentials |
| 300 | * <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning) |
| 301 | * </ul> |
| 302 | */ |
| 303 | public static enum Type { |
| 304 | WAIT, |
| 305 | CANCEL, |
| 306 | MODIFY, |
| 307 | AUTH, |
| 308 | CONTINUE |
| 309 | } |
| 310 | |
| 311 | /** |
| 312 | * A class to represent predefined error conditions. |
| 313 | */ |
| 314 | public static class Condition { |
| 315 | |
| 316 | public static final Condition interna_server_error = new Condition("internal-server-error"); |
| 317 | public static final Condition forbidden = new Condition("forbidden"); |
| 318 | public static final Condition bad_request = new Condition("bad-request"); |
| 319 | public static final Condition conflict = new Condition("conflict"); |
| 320 | public static final Condition feature_not_implemented = new Condition("feature-not-implemented"); |
| 321 | public static final Condition gone = new Condition("gone"); |
| 322 | public static final Condition item_not_found = new Condition("item-not-found"); |
| 323 | public static final Condition jid_malformed = new Condition("jid-malformed"); |
| 324 | public static final Condition no_acceptable = new Condition("not-acceptable"); |
| 325 | public static final Condition not_allowed = new Condition("not-allowed"); |
| 326 | public static final Condition not_authorized = new Condition("not-authorized"); |
| 327 | public static final Condition payment_required = new Condition("payment-required"); |
| 328 | public static final Condition recipient_unavailable = new Condition("recipient-unavailable"); |
| 329 | public static final Condition redirect = new Condition("redirect"); |
| 330 | public static final Condition registration_required = new Condition("registration-required"); |
| 331 | public static final Condition remote_server_error = new Condition("remote-server-error"); |
| 332 | public static final Condition remote_server_not_found = new Condition("remote-server-not-found"); |
| 333 | public static final Condition remote_server_timeout = new Condition("remote-server-timeout"); |
| 334 | public static final Condition resource_constraint = new Condition("resource-constraint"); |
| 335 | public static final Condition service_unavailable = new Condition("service-unavailable"); |
| 336 | public static final Condition subscription_required = new Condition("subscription-required"); |
| 337 | public static final Condition undefined_condition = new Condition("undefined-condition"); |
| 338 | public static final Condition unexpected_request = new Condition("unexpected-request"); |
| 339 | public static final Condition request_timeout = new Condition("request-timeout"); |
| 340 | |
| 341 | private String value; |
| 342 | |
| 343 | public Condition(String value) { |
| 344 | this.value = value; |
| 345 | } |
| 346 | |
| 347 | public String toString() { |
| 348 | return value; |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | |
| 353 | /** |
| 354 | * A class to represent the error specification used to infer common usage. |
| 355 | */ |
| 356 | private static class ErrorSpecification { |
| 357 | private int code; |
| 358 | private Type type; |
| 359 | private Condition condition; |
| 360 | private static Map<Condition, ErrorSpecification> instances = errorSpecifications(); |
| 361 | |
| 362 | private ErrorSpecification(Condition condition, Type type, int code) { |
| 363 | this.code = code; |
| 364 | this.type = type; |
| 365 | this.condition = condition; |
| 366 | } |
| 367 | |
| 368 | private static Map<Condition, ErrorSpecification> errorSpecifications() { |
| 369 | Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>(22); |
| 370 | instances.put(Condition.interna_server_error, new ErrorSpecification( |
| 371 | Condition.interna_server_error, Type.WAIT, 500)); |
| 372 | instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden, |
| 373 | Type.AUTH, 403)); |
| 374 | instances.put(Condition.bad_request, new XMPPError.ErrorSpecification( |
| 375 | Condition.bad_request, Type.MODIFY, 400)); |
| 376 | instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification( |
| 377 | Condition.item_not_found, Type.CANCEL, 404)); |
| 378 | instances.put(Condition.conflict, new XMPPError.ErrorSpecification( |
| 379 | Condition.conflict, Type.CANCEL, 409)); |
| 380 | instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification( |
| 381 | Condition.feature_not_implemented, Type.CANCEL, 501)); |
| 382 | instances.put(Condition.gone, new XMPPError.ErrorSpecification( |
| 383 | Condition.gone, Type.MODIFY, 302)); |
| 384 | instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification( |
| 385 | Condition.jid_malformed, Type.MODIFY, 400)); |
| 386 | instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification( |
| 387 | Condition.no_acceptable, Type.MODIFY, 406)); |
| 388 | instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification( |
| 389 | Condition.not_allowed, Type.CANCEL, 405)); |
| 390 | instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification( |
| 391 | Condition.not_authorized, Type.AUTH, 401)); |
| 392 | instances.put(Condition.payment_required, new XMPPError.ErrorSpecification( |
| 393 | Condition.payment_required, Type.AUTH, 402)); |
| 394 | instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification( |
| 395 | Condition.recipient_unavailable, Type.WAIT, 404)); |
| 396 | instances.put(Condition.redirect, new XMPPError.ErrorSpecification( |
| 397 | Condition.redirect, Type.MODIFY, 302)); |
| 398 | instances.put(Condition.registration_required, new XMPPError.ErrorSpecification( |
| 399 | Condition.registration_required, Type.AUTH, 407)); |
| 400 | instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification( |
| 401 | Condition.remote_server_not_found, Type.CANCEL, 404)); |
| 402 | instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification( |
| 403 | Condition.remote_server_timeout, Type.WAIT, 504)); |
| 404 | instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification( |
| 405 | Condition.remote_server_error, Type.CANCEL, 502)); |
| 406 | instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification( |
| 407 | Condition.resource_constraint, Type.WAIT, 500)); |
| 408 | instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification( |
| 409 | Condition.service_unavailable, Type.CANCEL, 503)); |
| 410 | instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification( |
| 411 | Condition.subscription_required, Type.AUTH, 407)); |
| 412 | instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification( |
| 413 | Condition.undefined_condition, Type.WAIT, 500)); |
| 414 | instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification( |
| 415 | Condition.unexpected_request, Type.WAIT, 400)); |
| 416 | instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification( |
| 417 | Condition.request_timeout, Type.CANCEL, 408)); |
| 418 | |
| 419 | return instances; |
| 420 | } |
| 421 | |
| 422 | protected static ErrorSpecification specFor(Condition condition) { |
| 423 | return instances.get(condition); |
| 424 | } |
| 425 | |
| 426 | /** |
| 427 | * Returns the error condition. |
| 428 | * |
| 429 | * @return the error condition. |
| 430 | */ |
| 431 | protected Condition getCondition() { |
| 432 | return condition; |
| 433 | } |
| 434 | |
| 435 | /** |
| 436 | * Returns the error type. |
| 437 | * |
| 438 | * @return the error type. |
| 439 | */ |
| 440 | protected Type getType() { |
| 441 | return type; |
| 442 | } |
| 443 | |
| 444 | /** |
| 445 | * Returns the error code. |
| 446 | * |
| 447 | * @return the error code. |
| 448 | */ |
| 449 | protected int getCode() { |
| 450 | return code; |
| 451 | } |
| 452 | } |
| 453 | } |