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 = ExceptionUtil.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 * Explains an {@link OsmTransferException} to the user. 407 * 408 * @param e the {@link OsmTransferException} 409 */ 410 public static void explainOsmTransferException(OsmTransferException e) { 411 if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) { 412 explainSecurityException(e); 413 return; 414 } 415 if (ExceptionUtil.getNestedException(e, SocketException.class) != null) { 416 explainNestedSocketException(e); 417 return; 418 } 419 if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) { 420 explainNestedUnkonwnHostException(e); 421 return; 422 } 423 if (ExceptionUtil.getNestedException(e, IOException.class) != null) { 424 explainNestedIOException(e); 425 return; 426 } 427 if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) { 428 explainNestedIllegalDataException(e); 429 return; 430 } 431 if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) { 432 explainNestedOfflineAccessException(e); 433 return; 434 } 435 if (e instanceof OsmApiInitializationException) { 436 explainOsmApiInitializationException((OsmApiInitializationException) e); 437 return; 438 } 439 440 if (e instanceof ChangesetClosedException) { 441 explainChangesetClosedException((ChangesetClosedException) e); 442 return; 443 } 444 445 if (e instanceof MissingOAuthAccessTokenException) { 446 explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e); 447 return; 448 } 449 450 if (e instanceof OsmApiException) { 451 OsmApiException oae = (OsmApiException) e; 452 switch(oae.getResponseCode()) { 453 case HttpURLConnection.HTTP_PRECON_FAILED: 454 explainPreconditionFailed(oae); 455 return; 456 case HttpURLConnection.HTTP_GONE: 457 explainGoneForUnknownPrimitive(oae); 458 return; 459 case HttpURLConnection.HTTP_INTERNAL_ERROR: 460 explainInternalServerError(oae); 461 return; 462 case HttpURLConnection.HTTP_BAD_REQUEST: 463 explainBadRequest(oae); 464 return; 465 case HttpURLConnection.HTTP_NOT_FOUND: 466 explainNotFound(oae); 467 return; 468 case HttpURLConnection.HTTP_CONFLICT: 469 explainConflict(oae); 470 return; 471 case HttpURLConnection.HTTP_UNAUTHORIZED: 472 explainAuthenticationFailed(oae); 473 return; 474 case HttpURLConnection.HTTP_FORBIDDEN: 475 explainAuthorizationFailed(oae); 476 return; 477 case HttpURLConnection.HTTP_CLIENT_TIMEOUT: 478 explainClientTimeout(oae); 479 return; 480 case 509: 481 explainBandwidthLimitExceeded(oae); 482 return; 483 default: 484 explainGenericHttpException(oae); 485 return; 486 } 487 } 488 explainGeneric(e); 489 } 490 491 /** 492 * explains the case of an error due to a delete request on an already deleted 493 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 494 * {@link OsmPrimitive} is causing the error. 495 * 496 * @param e the exception 497 */ 498 public static void explainGoneForUnknownPrimitive(OsmApiException e) { 499 HelpAwareOptionPane.showOptionDialog( 500 Main.parent, 501 ExceptionUtil.explainGoneForUnknownPrimitive(e), 502 tr("Object deleted"), 503 JOptionPane.ERROR_MESSAGE, 504 ht("/ErrorMessages#GoneForUnknownPrimitive") 505 ); 506 } 507 508 /** 509 * Explains an {@link Exception} to the user. 510 * 511 * @param e the {@link Exception} 512 */ 513 public static void explainException(Exception e) { 514 if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) { 515 explainNestedInvocationTargetException(e); 516 return; 517 } 518 if (e instanceof OsmTransferException) { 519 explainOsmTransferException((OsmTransferException) e); 520 return; 521 } 522 explainGeneric(e); 523 } 524}