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