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