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