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 message. 357 * 358 * @param e the exception 359 */ 360 public static void explainGenericHttpException(OsmApiException e) { 361 HelpAwareOptionPane.showOptionDialog( 362 Main.parent, 363 ExceptionUtil.explainClientTimeout(e), 364 tr("Communication with OSM server failed"), 365 JOptionPane.ERROR_MESSAGE, 366 ht("/ErrorMessages#GenericCommunicationError") 367 ); 368 } 369 370 /** 371 * Explains a {@link OsmApiException} which was thrown because accessing a protected 372 * resource was forbidden. 373 * 374 * @param e the exception 375 */ 376 public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) { 377 HelpAwareOptionPane.showOptionDialog( 378 Main.parent, 379 ExceptionUtil.explainMissingOAuthAccessTokenException(e), 380 tr("Authentication failed"), 381 JOptionPane.ERROR_MESSAGE, 382 ht("/ErrorMessages#MissingOAuthAccessToken") 383 ); 384 } 385 386 /** 387 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 388 * This is most likely happening when there is an error in the API URL or when 389 * local DNS services are not working. 390 * 391 * @param e the exception 392 */ 393 public static void explainNestedUnkonwnHostException(OsmTransferException e) { 394 HelpAwareOptionPane.showOptionDialog( 395 Main.parent, 396 ExceptionUtil.explainNestedUnknownHostException(e), 397 tr("Unknown host"), 398 JOptionPane.ERROR_MESSAGE, 399 ht("/ErrorMessages#UnknownHost") 400 ); 401 } 402 403 /** 404 * Explains an {@link OsmTransferException} to the user. 405 * 406 * @param e the {@link OsmTransferException} 407 */ 408 public static void explainOsmTransferException(OsmTransferException e) { 409 if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) { 410 explainSecurityException(e); 411 return; 412 } 413 if (ExceptionUtil.getNestedException(e, SocketException.class) != null) { 414 explainNestedSocketException(e); 415 return; 416 } 417 if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) { 418 explainNestedUnkonwnHostException(e); 419 return; 420 } 421 if (ExceptionUtil.getNestedException(e, IOException.class) != null) { 422 explainNestedIOException(e); 423 return; 424 } 425 if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) { 426 explainNestedIllegalDataException(e); 427 return; 428 } 429 if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) { 430 explainNestedOfflineAccessException(e); 431 return; 432 } 433 if (e instanceof OsmApiInitializationException) { 434 explainOsmApiInitializationException((OsmApiInitializationException) e); 435 return; 436 } 437 438 if (e instanceof ChangesetClosedException) { 439 explainChangesetClosedException((ChangesetClosedException) e); 440 return; 441 } 442 443 if (e instanceof MissingOAuthAccessTokenException) { 444 explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e); 445 return; 446 } 447 448 if (e instanceof OsmApiException) { 449 OsmApiException oae = (OsmApiException) e; 450 switch(oae.getResponseCode()) { 451 case HttpURLConnection.HTTP_PRECON_FAILED: 452 explainPreconditionFailed(oae); 453 return; 454 case HttpURLConnection.HTTP_GONE: 455 explainGoneForUnknownPrimitive(oae); 456 return; 457 case HttpURLConnection.HTTP_INTERNAL_ERROR: 458 explainInternalServerError(oae); 459 return; 460 case HttpURLConnection.HTTP_BAD_REQUEST: 461 explainBadRequest(oae); 462 return; 463 case HttpURLConnection.HTTP_NOT_FOUND: 464 explainNotFound(oae); 465 return; 466 case HttpURLConnection.HTTP_CONFLICT: 467 explainConflict(oae); 468 return; 469 case HttpURLConnection.HTTP_UNAUTHORIZED: 470 explainAuthenticationFailed(oae); 471 return; 472 case HttpURLConnection.HTTP_FORBIDDEN: 473 explainAuthorizationFailed(oae); 474 return; 475 case HttpURLConnection.HTTP_CLIENT_TIMEOUT: 476 explainClientTimeout(oae); 477 return; 478 case 509: 479 explainBandwidthLimitExceeded(oae); 480 return; 481 default: 482 explainGenericHttpException(oae); 483 return; 484 } 485 } 486 explainGeneric(e); 487 } 488 489 /** 490 * explains the case of an error due to a delete request on an already deleted 491 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 492 * {@link OsmPrimitive} is causing the error. 493 * 494 * @param e the exception 495 */ 496 public static void explainGoneForUnknownPrimitive(OsmApiException e) { 497 HelpAwareOptionPane.showOptionDialog( 498 Main.parent, 499 ExceptionUtil.explainGoneForUnknownPrimitive(e), 500 tr("Object deleted"), 501 JOptionPane.ERROR_MESSAGE, 502 ht("/ErrorMessages#GoneForUnknownPrimitive") 503 ); 504 } 505 506 /** 507 * Explains an {@link Exception} to the user. 508 * 509 * @param e the {@link Exception} 510 */ 511 public static void explainException(Exception e) { 512 if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) { 513 explainNestedInvocationTargetException(e); 514 return; 515 } 516 if (e instanceof OsmTransferException) { 517 explainOsmTransferException((OsmTransferException) e); 518 return; 519 } 520 explainGeneric(e); 521 } 522}