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