001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.io.IOException; 008import java.lang.reflect.InvocationTargetException; 009import java.net.HttpURLConnection; 010import java.net.SocketException; 011import java.net.UnknownHostException; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014 015import javax.swing.JOptionPane; 016 017import org.openstreetmap.josm.Main; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.io.ChangesetClosedException; 020import org.openstreetmap.josm.io.IllegalDataException; 021import org.openstreetmap.josm.io.MissingOAuthAccessTokenException; 022import org.openstreetmap.josm.io.OfflineAccessException; 023import org.openstreetmap.josm.io.OsmApi; 024import org.openstreetmap.josm.io.OsmApiException; 025import org.openstreetmap.josm.io.OsmApiInitializationException; 026import org.openstreetmap.josm.io.OsmTransferException; 027import org.openstreetmap.josm.tools.BugReportExceptionHandler; 028import org.openstreetmap.josm.tools.ExceptionUtil; 029 030/** 031 * This utility class provides static methods which explain various exceptions to the user. 032 * 033 */ 034public final class ExceptionDialogUtil { 035 036 /** 037 * just static utility functions. no constructor 038 */ 039 private ExceptionDialogUtil() { 040 } 041 042 /** 043 * handles an exception caught during OSM API initialization 044 * 045 * @param e the exception 046 */ 047 public static void explainOsmApiInitializationException(OsmApiInitializationException e) { 048 HelpAwareOptionPane.showOptionDialog( 049 Main.parent, 050 ExceptionUtil.explainOsmApiInitializationException(e), 051 tr("Error"), 052 JOptionPane.ERROR_MESSAGE, 053 ht("/ErrorMessages#OsmApiInitializationException") 054 ); 055 } 056 057 /** 058 * handles a ChangesetClosedException 059 * 060 * @param e the exception 061 */ 062 public static void explainChangesetClosedException(ChangesetClosedException e) { 063 HelpAwareOptionPane.showOptionDialog( 064 Main.parent, 065 ExceptionUtil.explainChangesetClosedException(e), 066 tr("Error"), 067 JOptionPane.ERROR_MESSAGE, 068 ht("/Action/Upload#ChangesetClosed") 069 ); 070 } 071 072 /** 073 * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412 074 * 075 * @param e the exception 076 */ 077 public static void explainPreconditionFailed(OsmApiException e) { 078 HelpAwareOptionPane.showOptionDialog( 079 Main.parent, 080 ExceptionUtil.explainPreconditionFailed(e), 081 tr("Precondition violation"), 082 JOptionPane.ERROR_MESSAGE, 083 ht("/ErrorMessages#OsmApiException") 084 ); 085 } 086 087 /** 088 * Explains an exception with a generic message dialog 089 * 090 * @param e the exception 091 */ 092 public static void explainGeneric(Exception e) { 093 Main.error(e); 094 BugReportExceptionHandler.handleException(e); 095 } 096 097 /** 098 * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}. 099 * This is most likely happening when user tries to access the OSM API from within an 100 * applet which wasn't loaded from the API server. 101 * 102 * @param e the exception 103 */ 104 105 public static void explainSecurityException(OsmTransferException e) { 106 HelpAwareOptionPane.showOptionDialog( 107 Main.parent, 108 ExceptionUtil.explainSecurityException(e), 109 tr("Security exception"), 110 JOptionPane.ERROR_MESSAGE, 111 ht("/ErrorMessages#SecurityException") 112 ); 113 } 114 115 /** 116 * Explains a {@link SocketException} which has caused an {@link OsmTransferException}. 117 * This is most likely because there's not connection to the Internet or because 118 * the remote server is not reachable. 119 * 120 * @param e the exception 121 */ 122 123 public static void explainNestedSocketException(OsmTransferException e) { 124 HelpAwareOptionPane.showOptionDialog( 125 Main.parent, 126 ExceptionUtil.explainNestedSocketException(e), 127 tr("Network exception"), 128 JOptionPane.ERROR_MESSAGE, 129 ht("/ErrorMessages#NestedSocketException") 130 ); 131 } 132 133 /** 134 * Explains a {@link IOException} which has caused an {@link OsmTransferException}. 135 * This is most likely happening when the communication with the remote server is 136 * interrupted for any reason. 137 * 138 * @param e the exception 139 */ 140 141 public static void explainNestedIOException(OsmTransferException e) { 142 HelpAwareOptionPane.showOptionDialog( 143 Main.parent, 144 ExceptionUtil.explainNestedIOException(e), 145 tr("IO Exception"), 146 JOptionPane.ERROR_MESSAGE, 147 ht("/ErrorMessages#NestedIOException") 148 ); 149 } 150 151 /** 152 * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}. 153 * This is most likely happening when JOSM tries to load data in an unsupported format. 154 * 155 * @param e the exception 156 */ 157 public static void explainNestedIllegalDataException(OsmTransferException e) { 158 HelpAwareOptionPane.showOptionDialog( 159 Main.parent, 160 ExceptionUtil.explainNestedIllegalDataException(e), 161 tr("Illegal Data"), 162 JOptionPane.ERROR_MESSAGE, 163 ht("/ErrorMessages#IllegalDataException") 164 ); 165 } 166 167 /** 168 * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}. 169 * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode. 170 * 171 * @param e the exception 172 * @since 7434 173 */ 174 public static void explainNestedOfflineAccessException(OsmTransferException e) { 175 HelpAwareOptionPane.showOptionDialog( 176 Main.parent, 177 ExceptionUtil.explainOfflineAccessException(e), 178 tr("Offline mode"), 179 JOptionPane.ERROR_MESSAGE, 180 ht("/ErrorMessages#OfflineAccessException") 181 ); 182 } 183 184 /** 185 * Explains a {@link InvocationTargetException } 186 * 187 * @param e the exception 188 */ 189 public static void explainNestedInvocationTargetException(Exception e) { 190 InvocationTargetException ex = getNestedException(e, InvocationTargetException.class); 191 if (ex != null) { 192 // Users should be able to submit a bug report for an invocation target exception 193 // 194 BugReportExceptionHandler.handleException(ex); 195 return; 196 } 197 } 198 199 /** 200 * Explains a {@link OsmApiException} which was thrown because of an internal server 201 * error in the OSM API server. 202 * 203 * @param e the exception 204 */ 205 206 public static void explainInternalServerError(OsmTransferException e) { 207 HelpAwareOptionPane.showOptionDialog( 208 Main.parent, 209 ExceptionUtil.explainInternalServerError(e), 210 tr("Internal Server Error"), 211 JOptionPane.ERROR_MESSAGE, 212 ht("/ErrorMessages#InternalServerError") 213 ); 214 } 215 216 /** 217 * Explains a {@link OsmApiException} which was thrown because of a bad 218 * request 219 * 220 * @param e the exception 221 */ 222 public static void explainBadRequest(OsmApiException e) { 223 HelpAwareOptionPane.showOptionDialog( 224 Main.parent, 225 ExceptionUtil.explainBadRequest(e), 226 tr("Bad Request"), 227 JOptionPane.ERROR_MESSAGE, 228 ht("/ErrorMessages#BadRequest") 229 ); 230 } 231 232 /** 233 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found 234 * on the server 235 * 236 * @param e the exception 237 */ 238 public static void explainNotFound(OsmApiException e) { 239 HelpAwareOptionPane.showOptionDialog( 240 Main.parent, 241 ExceptionUtil.explainNotFound(e), 242 tr("Not Found"), 243 JOptionPane.ERROR_MESSAGE, 244 ht("/ErrorMessages#NotFound") 245 ); 246 } 247 248 /** 249 * Explains a {@link OsmApiException} which was thrown because of a conflict 250 * 251 * @param e the exception 252 */ 253 public static void explainConflict(OsmApiException e) { 254 HelpAwareOptionPane.showOptionDialog( 255 Main.parent, 256 ExceptionUtil.explainConflict(e), 257 tr("Conflict"), 258 JOptionPane.ERROR_MESSAGE, 259 ht("/ErrorMessages#Conflict") 260 ); 261 } 262 263 /** 264 * Explains a {@link OsmApiException} which was thrown because the authentication at 265 * the OSM server failed 266 * 267 * @param e the exception 268 */ 269 public static void explainAuthenticationFailed(OsmApiException e) { 270 String msg; 271 if (OsmApi.isUsingOAuth()) { 272 msg = ExceptionUtil.explainFailedOAuthAuthentication(e); 273 } else { 274 msg = ExceptionUtil.explainFailedBasicAuthentication(e); 275 } 276 277 HelpAwareOptionPane.showOptionDialog( 278 Main.parent, 279 msg, 280 tr("Authentication Failed"), 281 JOptionPane.ERROR_MESSAGE, 282 ht("/ErrorMessages#AuthenticationFailed") 283 ); 284 } 285 286 /** 287 * Explains a {@link OsmApiException} which was thrown because accessing a protected 288 * resource was forbidden (HTTP 403). 289 * 290 * @param e the exception 291 */ 292 public static void explainAuthorizationFailed(OsmApiException e) { 293 294 Matcher m; 295 String msg; 296 String url = e.getAccessedUrl(); 297 Pattern p = Pattern.compile("https?://.*/api/0.6/(node|way|relation)/(\\d+)/(\\d+)"); 298 299 // Special case for individual access to redacted versions 300 // See http://wiki.openstreetmap.org/wiki/Open_Database_License/Changes_in_the_API 301 if (url != null && (m = p.matcher(url)).matches()) { 302 String type = m.group(1); 303 String id = m.group(2); 304 String version = m.group(3); 305 // {1} is the translation of "node", "way" or "relation" 306 msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.", 307 version, tr(type), id); 308 } else if (OsmApi.isUsingOAuth()) { 309 msg = ExceptionUtil.explainFailedOAuthAuthorisation(e); 310 } else { 311 msg = ExceptionUtil.explainFailedAuthorisation(e); 312 } 313 314 HelpAwareOptionPane.showOptionDialog( 315 Main.parent, 316 msg, 317 tr("Authorisation Failed"), 318 JOptionPane.ERROR_MESSAGE, 319 ht("/ErrorMessages#AuthorizationFailed") 320 ); 321 } 322 323 /** 324 * Explains a {@link OsmApiException} which was thrown because of a 325 * client timeout (HTTP 408) 326 * 327 * @param e the exception 328 */ 329 public static void explainClientTimeout(OsmApiException e) { 330 HelpAwareOptionPane.showOptionDialog( 331 Main.parent, 332 ExceptionUtil.explainClientTimeout(e), 333 tr("Client Time Out"), 334 JOptionPane.ERROR_MESSAGE, 335 ht("/ErrorMessages#ClientTimeOut") 336 ); 337 } 338 339 /** 340 * Explains a {@link OsmApiException} which was thrown because of a 341 * bandwidth limit (HTTP 509) 342 * 343 * @param e the exception 344 */ 345 public static void explainBandwidthLimitExceeded(OsmApiException e) { 346 HelpAwareOptionPane.showOptionDialog( 347 Main.parent, 348 ExceptionUtil.explainBandwidthLimitExceeded(e), 349 tr("Bandwidth Limit Exceeded"), 350 JOptionPane.ERROR_MESSAGE, 351 ht("/ErrorMessages#BandwidthLimit") 352 ); 353 } 354 355 /** 356 * Explains a {@link OsmApiException} with a generic error 357 * message. 358 * 359 * @param e the exception 360 */ 361 public static void explainGenericHttpException(OsmApiException e) { 362 HelpAwareOptionPane.showOptionDialog( 363 Main.parent, 364 ExceptionUtil.explainClientTimeout(e), 365 tr("Communication with OSM server failed"), 366 JOptionPane.ERROR_MESSAGE, 367 ht("/ErrorMessages#GenericCommunicationError") 368 ); 369 } 370 371 /** 372 * Explains a {@link OsmApiException} which was thrown because accessing a protected 373 * resource was forbidden. 374 * 375 * @param e the exception 376 */ 377 public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) { 378 HelpAwareOptionPane.showOptionDialog( 379 Main.parent, 380 ExceptionUtil.explainMissingOAuthAccessTokenException(e), 381 tr("Authentication failed"), 382 JOptionPane.ERROR_MESSAGE, 383 ht("/ErrorMessages#MissingOAuthAccessToken") 384 ); 385 } 386 387 /** 388 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 389 * This is most likely happening when there is an error in the API URL or when 390 * local DNS services are not working. 391 * 392 * @param e the exception 393 */ 394 395 public static void explainNestedUnkonwnHostException(OsmTransferException e) { 396 HelpAwareOptionPane.showOptionDialog( 397 Main.parent, 398 ExceptionUtil.explainNestedUnknownHostException(e), 399 tr("Unknown host"), 400 JOptionPane.ERROR_MESSAGE, 401 ht("/ErrorMessages#UnknownHost") 402 ); 403 } 404 405 /** 406 * Replies the first nested exception of type <code>nestedClass</code> (including 407 * the root exception <code>e</code>) or null, if no such exception is found. 408 * 409 * @param <T> 410 * @param e the root exception 411 * @param nestedClass the type of the nested exception 412 * @return the first nested exception of type <code>nestedClass</code> (including 413 * the root exception <code>e</code>) or null, if no such exception is found. 414 */ 415 protected static <T> T getNestedException(Exception e, Class<T> nestedClass) { 416 Throwable t = e; 417 while (t != null && !(nestedClass.isInstance(t))) { 418 t = t.getCause(); 419 } 420 if (t == null) 421 return null; 422 else if (nestedClass.isInstance(t)) 423 return nestedClass.cast(t); 424 return null; 425 } 426 427 /** 428 * Explains an {@link OsmTransferException} to the user. 429 * 430 * @param e the {@link OsmTransferException} 431 */ 432 public static void explainOsmTransferException(OsmTransferException e) { 433 if (getNestedException(e, SecurityException.class) != null) { 434 explainSecurityException(e); 435 return; 436 } 437 if (getNestedException(e, SocketException.class) != null) { 438 explainNestedSocketException(e); 439 return; 440 } 441 if (getNestedException(e, UnknownHostException.class) != null) { 442 explainNestedUnkonwnHostException(e); 443 return; 444 } 445 if (getNestedException(e, IOException.class) != null) { 446 explainNestedIOException(e); 447 return; 448 } 449 if (getNestedException(e, IllegalDataException.class) != null) { 450 explainNestedIllegalDataException(e); 451 return; 452 } 453 if (getNestedException(e, OfflineAccessException.class) != null) { 454 explainNestedOfflineAccessException(e); 455 return; 456 } 457 if (e instanceof OsmApiInitializationException) { 458 explainOsmApiInitializationException((OsmApiInitializationException) e); 459 return; 460 } 461 462 if (e instanceof ChangesetClosedException) { 463 explainChangesetClosedException((ChangesetClosedException)e); 464 return; 465 } 466 467 if (e instanceof MissingOAuthAccessTokenException) { 468 explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException)e); 469 return; 470 } 471 472 if (e instanceof OsmApiException) { 473 OsmApiException oae = (OsmApiException) e; 474 switch(oae.getResponseCode()) { 475 case HttpURLConnection.HTTP_PRECON_FAILED: 476 explainPreconditionFailed(oae); 477 return; 478 case HttpURLConnection.HTTP_GONE: 479 explainGoneForUnknownPrimitive(oae); 480 return; 481 case HttpURLConnection.HTTP_INTERNAL_ERROR: 482 explainInternalServerError(oae); 483 return; 484 case HttpURLConnection.HTTP_BAD_REQUEST: 485 explainBadRequest(oae); 486 return; 487 case HttpURLConnection.HTTP_NOT_FOUND: 488 explainNotFound(oae); 489 return; 490 case HttpURLConnection.HTTP_CONFLICT: 491 explainConflict(oae); 492 return; 493 case HttpURLConnection.HTTP_UNAUTHORIZED: 494 explainAuthenticationFailed(oae); 495 return; 496 case HttpURLConnection.HTTP_FORBIDDEN: 497 explainAuthorizationFailed(oae); 498 return; 499 case HttpURLConnection.HTTP_CLIENT_TIMEOUT: 500 explainClientTimeout(oae); 501 return; 502 case 509: 503 explainBandwidthLimitExceeded(oae); 504 return; 505 default: 506 explainGenericHttpException(oae); 507 return; 508 } 509 } 510 explainGeneric(e); 511 } 512 513 /** 514 * explains the case of an error due to a delete request on an already deleted 515 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 516 * {@link OsmPrimitive} is causing the error. 517 * 518 * @param e the exception 519 */ 520 public static void explainGoneForUnknownPrimitive(OsmApiException e) { 521 HelpAwareOptionPane.showOptionDialog( 522 Main.parent, 523 ExceptionUtil.explainGoneForUnknownPrimitive(e), 524 tr("Object deleted"), 525 JOptionPane.ERROR_MESSAGE, 526 ht("/ErrorMessages#GoneForUnknownPrimitive") 527 ); 528 } 529 530 /** 531 * Explains an {@link Exception} to the user. 532 * 533 * @param e the {@link Exception} 534 */ 535 public static void explainException(Exception e) { 536 if (getNestedException(e, InvocationTargetException.class) != null) { 537 explainNestedInvocationTargetException(e); 538 return; 539 } 540 if (e instanceof OsmTransferException) { 541 explainOsmTransferException((OsmTransferException) e); 542 return; 543 } 544 explainGeneric(e); 545 } 546}