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