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