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 BugReportExceptionHandler.handleException(ex); 197 } 198 } 199 200 /** 201 * Explains a {@link OsmApiException} which was thrown because of an internal server 202 * error in the OSM API server. 203 * 204 * @param e the exception 205 */ 206 207 public static void explainInternalServerError(OsmTransferException e) { 208 HelpAwareOptionPane.showOptionDialog( 209 Main.parent, 210 ExceptionUtil.explainInternalServerError(e), 211 tr("Internal Server Error"), 212 JOptionPane.ERROR_MESSAGE, 213 ht("/ErrorMessages#InternalServerError") 214 ); 215 } 216 217 /** 218 * Explains a {@link OsmApiException} which was thrown because of a bad 219 * request 220 * 221 * @param e the exception 222 */ 223 public static void explainBadRequest(OsmApiException e) { 224 HelpAwareOptionPane.showOptionDialog( 225 Main.parent, 226 ExceptionUtil.explainBadRequest(e), 227 tr("Bad Request"), 228 JOptionPane.ERROR_MESSAGE, 229 ht("/ErrorMessages#BadRequest") 230 ); 231 } 232 233 /** 234 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found 235 * on the server 236 * 237 * @param e the exception 238 */ 239 public static void explainNotFound(OsmApiException e) { 240 HelpAwareOptionPane.showOptionDialog( 241 Main.parent, 242 ExceptionUtil.explainNotFound(e), 243 tr("Not Found"), 244 JOptionPane.ERROR_MESSAGE, 245 ht("/ErrorMessages#NotFound") 246 ); 247 } 248 249 /** 250 * Explains a {@link OsmApiException} which was thrown because of a conflict 251 * 252 * @param e the exception 253 */ 254 public static void explainConflict(OsmApiException e) { 255 HelpAwareOptionPane.showOptionDialog( 256 Main.parent, 257 ExceptionUtil.explainConflict(e), 258 tr("Conflict"), 259 JOptionPane.ERROR_MESSAGE, 260 ht("/ErrorMessages#Conflict") 261 ); 262 } 263 264 /** 265 * Explains a {@link OsmApiException} which was thrown because the authentication at 266 * the OSM server failed 267 * 268 * @param e the exception 269 */ 270 public static void explainAuthenticationFailed(OsmApiException e) { 271 String msg; 272 if (OsmApi.isUsingOAuth()) { 273 msg = ExceptionUtil.explainFailedOAuthAuthentication(e); 274 } else { 275 msg = ExceptionUtil.explainFailedBasicAuthentication(e); 276 } 277 278 HelpAwareOptionPane.showOptionDialog( 279 Main.parent, 280 msg, 281 tr("Authentication failed"), 282 JOptionPane.ERROR_MESSAGE, 283 ht("/ErrorMessages#AuthenticationFailed") 284 ); 285 } 286 287 /** 288 * Explains a {@link OsmApiException} which was thrown because accessing a protected 289 * resource was forbidden (HTTP 403). 290 * 291 * @param e the exception 292 */ 293 public static void explainAuthorizationFailed(OsmApiException e) { 294 295 Matcher m; 296 String msg; 297 String url = e.getAccessedUrl(); 298 Pattern p = Pattern.compile("https?://.*/api/0.6/(node|way|relation)/(\\d+)/(\\d+)"); 299 300 // Special case for individual access to redacted versions 301 // See http://wiki.openstreetmap.org/wiki/Open_Database_License/Changes_in_the_API 302 if (url != null && (m = p.matcher(url)).matches()) { 303 String type = m.group(1); 304 String id = m.group(2); 305 String version = m.group(3); 306 // {1} is the translation of "node", "way" or "relation" 307 msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.", 308 version, tr(type), id); 309 } else if (OsmApi.isUsingOAuth()) { 310 msg = ExceptionUtil.explainFailedOAuthAuthorisation(e); 311 } else { 312 msg = ExceptionUtil.explainFailedAuthorisation(e); 313 } 314 315 HelpAwareOptionPane.showOptionDialog( 316 Main.parent, 317 msg, 318 tr("Authorisation Failed"), 319 JOptionPane.ERROR_MESSAGE, 320 ht("/ErrorMessages#AuthorizationFailed") 321 ); 322 } 323 324 /** 325 * Explains a {@link OsmApiException} which was thrown because of a 326 * client timeout (HTTP 408) 327 * 328 * @param e the exception 329 */ 330 public static void explainClientTimeout(OsmApiException e) { 331 HelpAwareOptionPane.showOptionDialog( 332 Main.parent, 333 ExceptionUtil.explainClientTimeout(e), 334 tr("Client Time Out"), 335 JOptionPane.ERROR_MESSAGE, 336 ht("/ErrorMessages#ClientTimeOut") 337 ); 338 } 339 340 /** 341 * Explains a {@link OsmApiException} which was thrown because of a 342 * bandwidth limit (HTTP 509) 343 * 344 * @param e the exception 345 */ 346 public static void explainBandwidthLimitExceeded(OsmApiException e) { 347 HelpAwareOptionPane.showOptionDialog( 348 Main.parent, 349 ExceptionUtil.explainBandwidthLimitExceeded(e), 350 tr("Bandwidth Limit Exceeded"), 351 JOptionPane.ERROR_MESSAGE, 352 ht("/ErrorMessages#BandwidthLimit") 353 ); 354 } 355 356 /** 357 * Explains a {@link OsmApiException} with a generic error message. 358 * 359 * @param e the exception 360 */ 361 public static void explainGenericHttpException(OsmApiException e) { 362 String body = e.getErrorBody(); 363 Object msg = null; 364 if ("text/html".equals(e.getContentType()) && body != null && body.startsWith("<") && body.contains("<html>")) { 365 msg = new HtmlPanel(body); 366 } else { 367 msg = ExceptionUtil.explainGeneric(e); 368 } 369 HelpAwareOptionPane.showOptionDialog( 370 Main.parent, 371 msg, 372 tr("Communication with OSM server failed"), 373 JOptionPane.ERROR_MESSAGE, 374 ht("/ErrorMessages#GenericCommunicationError") 375 ); 376 } 377 378 /** 379 * Explains a {@link OsmApiException} which was thrown because accessing a protected 380 * resource was forbidden. 381 * 382 * @param e the exception 383 */ 384 public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) { 385 HelpAwareOptionPane.showOptionDialog( 386 Main.parent, 387 ExceptionUtil.explainMissingOAuthAccessTokenException(e), 388 tr("Authentication failed"), 389 JOptionPane.ERROR_MESSAGE, 390 ht("/ErrorMessages#MissingOAuthAccessToken") 391 ); 392 } 393 394 /** 395 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 396 * This is most likely happening when there is an error in the API URL or when 397 * local DNS services are not working. 398 * 399 * @param e the exception 400 */ 401 public static void explainNestedUnkonwnHostException(OsmTransferException e) { 402 HelpAwareOptionPane.showOptionDialog( 403 Main.parent, 404 ExceptionUtil.explainNestedUnknownHostException(e), 405 tr("Unknown host"), 406 JOptionPane.ERROR_MESSAGE, 407 ht("/ErrorMessages#UnknownHost") 408 ); 409 } 410 411 /** 412 * Explains an {@link OsmTransferException} to the user. 413 * 414 * @param e the {@link OsmTransferException} 415 */ 416 public static void explainOsmTransferException(OsmTransferException e) { 417 if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) { 418 explainSecurityException(e); 419 return; 420 } 421 if (ExceptionUtil.getNestedException(e, SocketException.class) != null) { 422 explainNestedSocketException(e); 423 return; 424 } 425 if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) { 426 explainNestedUnkonwnHostException(e); 427 return; 428 } 429 if (ExceptionUtil.getNestedException(e, IOException.class) != null) { 430 explainNestedIOException(e); 431 return; 432 } 433 if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) { 434 explainNestedIllegalDataException(e); 435 return; 436 } 437 if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) { 438 explainNestedOfflineAccessException(e); 439 return; 440 } 441 if (e instanceof OsmApiInitializationException) { 442 explainOsmApiInitializationException((OsmApiInitializationException) e); 443 return; 444 } 445 446 if (e instanceof ChangesetClosedException) { 447 explainChangesetClosedException((ChangesetClosedException) e); 448 return; 449 } 450 451 if (e instanceof MissingOAuthAccessTokenException) { 452 explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e); 453 return; 454 } 455 456 if (e instanceof OsmApiException) { 457 OsmApiException oae = (OsmApiException) e; 458 switch(oae.getResponseCode()) { 459 case HttpURLConnection.HTTP_PRECON_FAILED: 460 explainPreconditionFailed(oae); 461 return; 462 case HttpURLConnection.HTTP_GONE: 463 explainGoneForUnknownPrimitive(oae); 464 return; 465 case HttpURLConnection.HTTP_INTERNAL_ERROR: 466 explainInternalServerError(oae); 467 return; 468 case HttpURLConnection.HTTP_BAD_REQUEST: 469 explainBadRequest(oae); 470 return; 471 case HttpURLConnection.HTTP_NOT_FOUND: 472 explainNotFound(oae); 473 return; 474 case HttpURLConnection.HTTP_CONFLICT: 475 explainConflict(oae); 476 return; 477 case HttpURLConnection.HTTP_UNAUTHORIZED: 478 explainAuthenticationFailed(oae); 479 return; 480 case HttpURLConnection.HTTP_FORBIDDEN: 481 explainAuthorizationFailed(oae); 482 return; 483 case HttpURLConnection.HTTP_CLIENT_TIMEOUT: 484 explainClientTimeout(oae); 485 return; 486 case 509: case 429: 487 explainBandwidthLimitExceeded(oae); 488 return; 489 default: 490 explainGenericHttpException(oae); 491 return; 492 } 493 } 494 explainGeneric(e); 495 } 496 497 /** 498 * explains the case of an error due to a delete request on an already deleted 499 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 500 * {@link OsmPrimitive} is causing the error. 501 * 502 * @param e the exception 503 */ 504 public static void explainGoneForUnknownPrimitive(OsmApiException e) { 505 HelpAwareOptionPane.showOptionDialog( 506 Main.parent, 507 ExceptionUtil.explainGoneForUnknownPrimitive(e), 508 tr("Object deleted"), 509 JOptionPane.ERROR_MESSAGE, 510 ht("/ErrorMessages#GoneForUnknownPrimitive") 511 ); 512 } 513 514 /** 515 * Explains an {@link Exception} to the user. 516 * 517 * @param e the {@link Exception} 518 */ 519 public static void explainException(Exception e) { 520 if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) { 521 explainNestedInvocationTargetException(e); 522 return; 523 } 524 if (e instanceof OsmTransferException) { 525 explainOsmTransferException((OsmTransferException) e); 526 return; 527 } 528 explainGeneric(e); 529 } 530}