001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.io.IOException; 008import java.net.HttpURLConnection; 009import java.net.MalformedURLException; 010import java.net.SocketException; 011import java.net.URL; 012import java.net.UnknownHostException; 013import java.text.DateFormat; 014import java.text.ParseException; 015import java.util.Collection; 016import java.util.Date; 017import java.util.Objects; 018import java.util.Optional; 019import java.util.TreeSet; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 024import org.openstreetmap.josm.data.osm.Node; 025import org.openstreetmap.josm.data.osm.OsmPrimitive; 026import org.openstreetmap.josm.data.osm.Relation; 027import org.openstreetmap.josm.data.osm.Way; 028import org.openstreetmap.josm.io.ChangesetClosedException; 029import org.openstreetmap.josm.io.IllegalDataException; 030import org.openstreetmap.josm.io.MissingOAuthAccessTokenException; 031import org.openstreetmap.josm.io.OfflineAccessException; 032import org.openstreetmap.josm.io.OsmApi; 033import org.openstreetmap.josm.io.OsmApiException; 034import org.openstreetmap.josm.io.OsmApiInitializationException; 035import org.openstreetmap.josm.io.OsmTransferException; 036import org.openstreetmap.josm.io.auth.CredentialsManager; 037import org.openstreetmap.josm.tools.date.DateUtils; 038 039/** 040 * Utilities for exception handling. 041 * @since 2097 042 */ 043public final class ExceptionUtil { 044 045 private ExceptionUtil() { 046 // Hide default constructor for utils classes 047 } 048 049 /** 050 * Explains an exception caught during OSM API initialization. 051 * 052 * @param e the exception 053 * @return The HTML formatted error message to display 054 */ 055 public static String explainOsmApiInitializationException(OsmApiInitializationException e) { 056 Logging.error(e); 057 return tr( 058 "<html>Failed to initialize communication with the OSM server {0}.<br>" 059 + "Check the server URL in your preferences and your internet connection.", 060 OsmApi.getOsmApi().getServerUrl())+"</html>"; 061 } 062 063 /** 064 * Explains a {@link OsmApiException} which was thrown because accessing a protected 065 * resource was forbidden. 066 * 067 * @param e the exception 068 * @return The HTML formatted error message to display 069 */ 070 public static String explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) { 071 Logging.error(e); 072 return tr( 073 "<html>Failed to authenticate at the OSM server ''{0}''.<br>" 074 + "You are using OAuth to authenticate but currently there is no<br>" 075 + "OAuth Access Token configured.<br>" 076 + "Please open the Preferences Dialog and generate or enter an Access Token." 077 + "</html>", 078 OsmApi.getOsmApi().getServerUrl() 079 ); 080 } 081 082 /** 083 * Parses a precondition failure response from the server and attempts to get more information about it 084 * @param msg The message from the server 085 * @return The OSM primitive that caused the problem and a collection of primitives that e.g. refer to it 086 */ 087 public static Pair<OsmPrimitive, Collection<OsmPrimitive>> parsePreconditionFailed(String msg) { 088 if (msg == null) 089 return null; 090 final String ids = "(\\d+(?:,\\d+)*)"; 091 final Collection<OsmPrimitive> refs = new TreeSet<>(); // error message can contain several times the same way 092 Matcher m; 093 m = Pattern.compile(".*Node (\\d+) is still used by relations? " + ids + ".*").matcher(msg); 094 if (m.matches()) { 095 OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 096 for (String s : m.group(2).split(",")) { 097 refs.add(new Relation(Long.parseLong(s))); 098 } 099 return Pair.create(n, refs); 100 } 101 m = Pattern.compile(".*Node (\\d+) is still used by ways? " + ids + ".*").matcher(msg); 102 if (m.matches()) { 103 OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 104 for (String s : m.group(2).split(",")) { 105 refs.add(new Way(Long.parseLong(s))); 106 } 107 return Pair.create(n, refs); 108 } 109 m = Pattern.compile(".*The relation (\\d+) is used in relations? " + ids + ".*").matcher(msg); 110 if (m.matches()) { 111 OsmPrimitive n = new Relation(Long.parseLong(m.group(1))); 112 for (String s : m.group(2).split(",")) { 113 refs.add(new Relation(Long.parseLong(s))); 114 } 115 return Pair.create(n, refs); 116 } 117 m = Pattern.compile(".*Way (\\d+) is still used by relations? " + ids + ".*").matcher(msg); 118 if (m.matches()) { 119 OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 120 for (String s : m.group(2).split(",")) { 121 refs.add(new Relation(Long.parseLong(s))); 122 } 123 return Pair.create(n, refs); 124 } 125 m = Pattern.compile(".*Way (\\d+) requires the nodes with id in " + ids + ".*").matcher(msg); 126 // ... ", which either do not exist, or are not visible" 127 if (m.matches()) { 128 OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 129 for (String s : m.group(2).split(",")) { 130 refs.add(new Node(Long.parseLong(s))); 131 } 132 return Pair.create(n, refs); 133 } 134 return null; 135 } 136 137 /** 138 * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412 139 * 140 * @param e the exception 141 * @return The HTML formatted error message to display 142 */ 143 public static String explainPreconditionFailed(OsmApiException e) { 144 Logging.error(e); 145 Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict = parsePreconditionFailed(e.getErrorHeader()); 146 if (conflict != null) { 147 OsmPrimitive firstRefs = conflict.b.iterator().next(); 148 String objId = Long.toString(conflict.a.getId()); 149 Collection<Long> refIds = Utils.transform(conflict.b, OsmPrimitive::getId); 150 String refIdsString = refIds.size() == 1 ? refIds.iterator().next().toString() : refIds.toString(); 151 if (conflict.a instanceof Node) { 152 if (firstRefs instanceof Node) { 153 return "<html>" + trn( 154 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 155 + " It is still referred to by node {1}.<br>" 156 + "Please load the node, remove the reference to the node, and upload again.", 157 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 158 + " It is still referred to by nodes {1}.<br>" 159 + "Please load the nodes, remove the reference to the node, and upload again.", 160 conflict.b.size(), objId, refIdsString) + "</html>"; 161 } else if (firstRefs instanceof Way) { 162 return "<html>" + trn( 163 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 164 + " It is still referred to by way {1}.<br>" 165 + "Please load the way, remove the reference to the node, and upload again.", 166 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 167 + " It is still referred to by ways {1}.<br>" 168 + "Please load the ways, remove the reference to the node, and upload again.", 169 conflict.b.size(), objId, refIdsString) + "</html>"; 170 } else if (firstRefs instanceof Relation) { 171 return "<html>" + trn( 172 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 173 + " It is still referred to by relation {1}.<br>" 174 + "Please load the relation, remove the reference to the node, and upload again.", 175 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 176 + " It is still referred to by relations {1}.<br>" 177 + "Please load the relations, remove the reference to the node, and upload again.", 178 conflict.b.size(), objId, refIdsString) + "</html>"; 179 } else { 180 throw new IllegalStateException(); 181 } 182 } else if (conflict.a instanceof Way) { 183 if (firstRefs instanceof Node) { 184 return "<html>" + trn( 185 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 186 + " It is still referred to by node {1}.<br>" 187 + "Please load the node, remove the reference to the way, and upload again.", 188 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 189 + " It is still referred to by nodes {1}.<br>" 190 + "Please load the nodes, remove the reference to the way, and upload again.", 191 conflict.b.size(), objId, refIdsString) + "</html>"; 192 } else if (firstRefs instanceof Way) { 193 return "<html>" + trn( 194 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 195 + " It is still referred to by way {1}.<br>" 196 + "Please load the way, remove the reference to the way, and upload again.", 197 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 198 + " It is still referred to by ways {1}.<br>" 199 + "Please load the ways, remove the reference to the way, and upload again.", 200 conflict.b.size(), objId, refIdsString) + "</html>"; 201 } else if (firstRefs instanceof Relation) { 202 return "<html>" + trn( 203 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 204 + " It is still referred to by relation {1}.<br>" 205 + "Please load the relation, remove the reference to the way, and upload again.", 206 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 207 + " It is still referred to by relations {1}.<br>" 208 + "Please load the relations, remove the reference to the way, and upload again.", 209 conflict.b.size(), objId, refIdsString) + "</html>"; 210 } else { 211 throw new IllegalStateException(); 212 } 213 } else if (conflict.a instanceof Relation) { 214 if (firstRefs instanceof Node) { 215 return "<html>" + trn( 216 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 217 + " It is still referred to by node {1}.<br>" 218 + "Please load the node, remove the reference to the relation, and upload again.", 219 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 220 + " It is still referred to by nodes {1}.<br>" 221 + "Please load the nodes, remove the reference to the relation, and upload again.", 222 conflict.b.size(), objId, refIdsString) + "</html>"; 223 } else if (firstRefs instanceof Way) { 224 return "<html>" + trn( 225 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 226 + " It is still referred to by way {1}.<br>" 227 + "Please load the way, remove the reference to the relation, and upload again.", 228 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 229 + " It is still referred to by ways {1}.<br>" 230 + "Please load the ways, remove the reference to the relation, and upload again.", 231 conflict.b.size(), objId, refIdsString) + "</html>"; 232 } else if (firstRefs instanceof Relation) { 233 return "<html>" + trn( 234 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 235 + " It is still referred to by relation {1}.<br>" 236 + "Please load the relation, remove the reference to the relation, and upload again.", 237 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 238 + " It is still referred to by relations {1}.<br>" 239 + "Please load the relations, remove the reference to the relation, and upload again.", 240 conflict.b.size(), objId, refIdsString) + "</html>"; 241 } else { 242 throw new IllegalStateException(); 243 } 244 } else { 245 throw new IllegalStateException(); 246 } 247 } else { 248 return tr( 249 "<html>Uploading to the server <strong>failed</strong> because your current<br>" 250 + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>", 251 Utils.escapeReservedCharactersHTML(e.getMessage())); 252 } 253 } 254 255 /** 256 * Explains a {@link OsmApiException} which was thrown because the authentication at 257 * the OSM server failed, with basic authentication. 258 * 259 * @param e the exception 260 * @return The HTML formatted error message to display 261 */ 262 public static String explainFailedBasicAuthentication(OsmApiException e) { 263 Logging.error(e); 264 return tr("<html>" 265 + "Authentication at the OSM server with the username ''{0}'' failed.<br>" 266 + "Please check the username and the password in the JOSM preferences." 267 + "</html>", 268 e.getLogin() != null ? e.getLogin() : CredentialsManager.getInstance().getUsername() 269 ); 270 } 271 272 /** 273 * Explains a {@link OsmApiException} which was thrown because the authentication at 274 * the OSM server failed, with OAuth authentication. 275 * 276 * @param e the exception 277 * @return The HTML formatted error message to display 278 */ 279 public static String explainFailedOAuthAuthentication(OsmApiException e) { 280 Logging.error(e); 281 return tr("<html>" 282 + "Authentication at the OSM server with the OAuth token ''{0}'' failed.<br>" 283 + "Please launch the preferences dialog and retrieve another OAuth token." 284 + "</html>", 285 OAuthAccessTokenHolder.getInstance().getAccessTokenKey() 286 ); 287 } 288 289 /** 290 * Explains a {@link OsmApiException} which was thrown because accessing a protected 291 * resource was forbidden (HTTP 403), without OAuth authentication. 292 * 293 * @param e the exception 294 * @return The HTML formatted error message to display 295 */ 296 public static String explainFailedAuthorisation(OsmApiException e) { 297 Logging.error(e); 298 String header = e.getErrorHeader(); 299 String body = e.getErrorBody(); 300 String msg; 301 if (header != null) { 302 if (body != null && !header.equals(body)) { 303 msg = header + " (" + body + ')'; 304 } else { 305 msg = header; 306 } 307 } else { 308 msg = body; 309 } 310 311 if (msg != null && !msg.isEmpty()) { 312 return tr("<html>" 313 + "Authorisation at the OSM server failed.<br>" 314 + "The server reported the following error:<br>" 315 + "''{0}''" 316 + "</html>", 317 msg 318 ); 319 } else { 320 return tr("<html>" 321 + "Authorisation at the OSM server failed.<br>" 322 + "</html>" 323 ); 324 } 325 } 326 327 /** 328 * Explains a {@link OsmApiException} which was thrown because accessing a protected 329 * resource was forbidden (HTTP 403), with OAuth authentication. 330 * 331 * @param e the exception 332 * @return The HTML formatted error message to display 333 */ 334 public static String explainFailedOAuthAuthorisation(OsmApiException e) { 335 Logging.error(e); 336 return tr("<html>" 337 + "Authorisation at the OSM server with the OAuth token ''{0}'' failed.<br>" 338 + "The token is not authorised to access the protected resource<br>" 339 + "''{1}''.<br>" 340 + "Please launch the preferences dialog and retrieve another OAuth token." 341 + "</html>", 342 OAuthAccessTokenHolder.getInstance().getAccessTokenKey(), 343 e.getAccessedUrl() == null ? tr("unknown") : e.getAccessedUrl() 344 ); 345 } 346 347 /** 348 * Explains an OSM API exception because of a client timeout (HTTP 408). 349 * 350 * @param e the exception 351 * @return The HTML formatted error message to display 352 */ 353 public static String explainClientTimeout(OsmApiException e) { 354 Logging.error(e); 355 return tr("<html>" 356 + "Communication with the OSM server ''{0}'' timed out. Please retry later." 357 + "</html>", 358 getUrlFromException(e) 359 ); 360 } 361 362 /** 363 * Replies a generic error message for an OSM API exception 364 * 365 * @param e the exception 366 * @return The HTML formatted error message to display 367 */ 368 public static String explainGenericOsmApiException(OsmApiException e) { 369 Logging.error(e); 370 return tr("<html>" 371 + "Communication with the OSM server ''{0}''failed. The server replied<br>" 372 + "the following error code and the following error message:<br>" 373 + "<strong>Error code:<strong> {1}<br>" 374 + "<strong>Error message (untranslated)</strong>: {2}" 375 + "</html>", 376 getUrlFromException(e), 377 e.getResponseCode(), 378 Optional.ofNullable(Optional.ofNullable(e.getErrorHeader()).orElseGet(e::getErrorBody)) 379 .orElse(tr("no error message available")) 380 ); 381 } 382 383 /** 384 * Explains an error due to a 409 conflict 385 * 386 * @param e the exception 387 * @return The HTML formatted error message to display 388 */ 389 public static String explainConflict(OsmApiException e) { 390 Logging.error(e); 391 String msg = e.getErrorHeader(); 392 if (msg != null) { 393 Matcher m = Pattern.compile("The changeset (\\d+) was closed at (.*)").matcher(msg); 394 if (m.matches()) { 395 long changesetId = Long.parseLong(m.group(1)); 396 Date closeDate = null; 397 try { 398 closeDate = DateUtils.newOsmApiDateTimeFormat().parse(m.group(2)); 399 } catch (ParseException ex) { 400 Logging.error(tr("Failed to parse date ''{0}'' replied by server.", m.group(2))); 401 Logging.error(ex); 402 } 403 if (closeDate == null) { 404 msg = tr( 405 "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.", 406 changesetId 407 ); 408 } else { 409 msg = tr( 410 "<html>Closing of changeset <strong>{0}</strong> failed<br>" 411 +" because it has already been closed on {1}.", 412 changesetId, 413 DateUtils.formatDateTime(closeDate, DateFormat.DEFAULT, DateFormat.DEFAULT) 414 ); 415 } 416 return msg; 417 } 418 msg = tr( 419 "<html>The server reported that it has detected a conflict.<br>" + 420 "Error message (untranslated):<br>{0}</html>", 421 msg 422 ); 423 } else { 424 msg = tr( 425 "<html>The server reported that it has detected a conflict."); 426 } 427 return msg.endsWith("</html>") ? msg : (msg + "</html>"); 428 } 429 430 /** 431 * Explains an exception thrown during upload because the changeset which data is 432 * uploaded to is already closed. 433 * 434 * @param e the exception 435 * @return The HTML formatted error message to display 436 */ 437 public static String explainChangesetClosedException(ChangesetClosedException e) { 438 Logging.error(e); 439 return tr( 440 "<html>Failed to upload to changeset <strong>{0}</strong><br>" 441 +"because it has already been closed on {1}.", 442 e.getChangesetId(), 443 e.getClosedOn() == null ? "?" : DateUtils.formatDateTime(e.getClosedOn(), DateFormat.DEFAULT, DateFormat.DEFAULT) 444 ); 445 } 446 447 /** 448 * Explains an exception with a generic message dialog 449 * 450 * @param e the exception 451 * @return The HTML formatted error message to display 452 */ 453 public static String explainGeneric(Exception e) { 454 String msg = e.getMessage(); 455 if (msg == null || msg.trim().isEmpty()) { 456 msg = e.toString(); 457 } 458 Logging.error(e); 459 return Utils.escapeReservedCharactersHTML(msg); 460 } 461 462 /** 463 * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}. 464 * This is most likely happening when user tries to access the OSM API from within an 465 * applet which wasn't loaded from the API server. 466 * 467 * @param e the exception 468 * @return The HTML formatted error message to display 469 */ 470 public static String explainSecurityException(OsmTransferException e) { 471 String apiUrl = e.getUrl(); 472 String host = tr("unknown"); 473 try { 474 host = new URL(apiUrl).getHost(); 475 } catch (MalformedURLException ex) { 476 // shouldn't happen 477 Logging.trace(ex); 478 } 479 480 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>" 481 + "for security reasons. This is most likely because you are running<br>" 482 + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host)+"</html>"; 483 } 484 485 /** 486 * Explains a {@link SocketException} which has caused an {@link OsmTransferException}. 487 * This is most likely because there's not connection to the Internet or because 488 * the remote server is not reachable. 489 * 490 * @param e the exception 491 * @return The HTML formatted error message to display 492 */ 493 public static String explainNestedSocketException(OsmTransferException e) { 494 Logging.error(e); 495 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 496 + "Please check your internet connection.", e.getUrl())+"</html>"; 497 } 498 499 /** 500 * Explains a {@link IOException} which has caused an {@link OsmTransferException}. 501 * This is most likely happening when the communication with the remote server is 502 * interrupted for any reason. 503 * 504 * @param e the exception 505 * @return The HTML formatted error message to display 506 */ 507 public static String explainNestedIOException(OsmTransferException e) { 508 IOException ioe = getNestedException(e, IOException.class); 509 Logging.error(e); 510 return tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>" 511 + "due to a problem with transferring data.<br>" 512 + "Details (untranslated): {1}</html>", 513 e != null ? e.getUrl() : "null", 514 ioe != null ? ioe.getMessage() : "null"); 515 } 516 517 /** 518 * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}. 519 * This is most likely happening when JOSM tries to load data in an unsupported format. 520 * 521 * @param e the exception 522 * @return The HTML formatted error message to display 523 */ 524 public static String explainNestedIllegalDataException(OsmTransferException e) { 525 IllegalDataException ide = getNestedException(e, IllegalDataException.class); 526 Logging.error(e); 527 return tr("<html>Failed to download data. " 528 + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>" 529 + "<br>Details (untranslated): {0}</html>", ide != null ? ide.getMessage() : "null"); 530 } 531 532 /** 533 * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}. 534 * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode. 535 * 536 * @param e the exception 537 * @return The HTML formatted error message to display 538 * @since 7434 539 */ 540 public static String explainOfflineAccessException(OsmTransferException e) { 541 OfflineAccessException oae = getNestedException(e, OfflineAccessException.class); 542 Logging.error(e); 543 return tr("<html>Failed to download data.<br>" 544 + "<br>Details: {0}</html>", oae != null ? oae.getMessage() : "null"); 545 } 546 547 /** 548 * Explains a {@link OsmApiException} which was thrown because of an internal server 549 * error in the OSM API server. 550 * 551 * @param e the exception 552 * @return The HTML formatted error message to display 553 */ 554 public static String explainInternalServerError(OsmTransferException e) { 555 Logging.error(e); 556 return tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>" 557 + "This is most likely a temporary problem. Please try again later.", e.getUrl())+"</html>"; 558 } 559 560 /** 561 * Explains a {@link OsmApiException} which was thrown because of a bad request. 562 * 563 * @param e the exception 564 * @return The HTML formatted error message to display 565 */ 566 public static String explainBadRequest(OsmApiException e) { 567 String message = tr("The OSM server ''{0}'' reported a bad request.<br>", getUrlFromException(e)); 568 String errorHeader = e.getErrorHeader(); 569 if (errorHeader != null && (errorHeader.startsWith("The maximum bbox") || 570 errorHeader.startsWith("You requested too many nodes"))) { 571 message += "<br>" 572 + tr("The area you tried to download is too big or your request was too large." 573 + "<br>Either request a smaller area or use an export file provided by the OSM community."); 574 } else if (errorHeader != null) { 575 message += tr("<br>Error message(untranslated): {0}", errorHeader); 576 } 577 Logging.error(e); 578 return "<html>" + message + "</html>"; 579 } 580 581 /** 582 * Explains a {@link OsmApiException} which was thrown because of 583 * bandwidth limit exceeded (HTTP error 509) 584 * 585 * @param e the exception 586 * @return The HTML formatted error message to display 587 */ 588 public static String explainBandwidthLimitExceeded(OsmApiException e) { 589 Logging.error(e); 590 // TODO: Write a proper error message 591 return explainGenericOsmApiException(e); 592 } 593 594 /** 595 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found. 596 * 597 * @param e the exception 598 * @return The HTML formatted error message to display 599 */ 600 public static String explainNotFound(OsmApiException e) { 601 String message = tr("The OSM server ''{0}'' does not know about an object<br>" 602 + "you tried to read, update, or delete. Either the respective object<br>" 603 + "does not exist on the server or you are using an invalid URL to access<br>" 604 + "it. Please carefully check the server''s address ''{0}'' for typos.", 605 getUrlFromException(e)); 606 Logging.error(e); 607 return "<html>" + message + "</html>"; 608 } 609 610 /** 611 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 612 * This is most likely happening when there is an error in the API URL or when 613 * local DNS services are not working. 614 * 615 * @param e the exception 616 * @return The HTML formatted error message to display 617 */ 618 public static String explainNestedUnknownHostException(OsmTransferException e) { 619 String apiUrl = e.getUrl(); 620 String host = tr("unknown"); 621 try { 622 host = new URL(apiUrl).getHost(); 623 } catch (MalformedURLException ex) { 624 // shouldn't happen 625 Logging.trace(e); 626 } 627 628 Logging.error(e); 629 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 630 + "Host name ''{1}'' could not be resolved. <br>" 631 + "Please check the API URL in your preferences and your internet connection.", apiUrl, host)+"</html>"; 632 } 633 634 /** 635 * Replies the first nested exception of type <code>nestedClass</code> (including 636 * the root exception <code>e</code>) or null, if no such exception is found. 637 * 638 * @param <T> nested exception type 639 * @param e the root exception 640 * @param nestedClass the type of the nested exception 641 * @return the first nested exception of type <code>nestedClass</code> (including 642 * the root exception <code>e</code>) or null, if no such exception is found. 643 * @since 8470 644 */ 645 public static <T> T getNestedException(Exception e, Class<T> nestedClass) { 646 Throwable t = e; 647 while (t != null && !(nestedClass.isInstance(t))) { 648 t = t.getCause(); 649 } 650 if (t == null) 651 return null; 652 else if (nestedClass.isInstance(t)) 653 return nestedClass.cast(t); 654 return null; 655 } 656 657 /** 658 * Explains an {@link OsmTransferException} to the user. 659 * 660 * @param e the {@link OsmTransferException} 661 * @return The HTML formatted error message to display 662 */ 663 public static String explainOsmTransferException(OsmTransferException e) { 664 Objects.requireNonNull(e, "e"); 665 if (getNestedException(e, SecurityException.class) != null) 666 return explainSecurityException(e); 667 if (getNestedException(e, SocketException.class) != null) 668 return explainNestedSocketException(e); 669 if (getNestedException(e, UnknownHostException.class) != null) 670 return explainNestedUnknownHostException(e); 671 if (getNestedException(e, IOException.class) != null) 672 return explainNestedIOException(e); 673 if (e instanceof OsmApiInitializationException) 674 return explainOsmApiInitializationException((OsmApiInitializationException) e); 675 676 if (e instanceof ChangesetClosedException) 677 return explainChangesetClosedException((ChangesetClosedException) e); 678 679 if (e instanceof OsmApiException) { 680 OsmApiException oae = (OsmApiException) e; 681 if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) 682 return explainPreconditionFailed(oae); 683 if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE) 684 return explainGoneForUnknownPrimitive(oae); 685 if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) 686 return explainInternalServerError(oae); 687 if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST) 688 return explainBadRequest(oae); 689 if (oae.getResponseCode() == 509) 690 return explainBandwidthLimitExceeded(oae); 691 } 692 return explainGeneric(e); 693 } 694 695 /** 696 * explains the case of an error due to a delete request on an already deleted 697 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 698 * {@link OsmPrimitive} is causing the error. 699 * 700 * @param e the exception 701 * @return The HTML formatted error message to display 702 */ 703 public static String explainGoneForUnknownPrimitive(OsmApiException e) { 704 return tr( 705 "<html>The server reports that an object is deleted.<br>" 706 + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> " 707 + "<strong>Downloading failed</strong> if you tried to download this object.<br>" 708 + "<br>" 709 + "The error message is:<br>" + "{0}" 710 + "</html>", Utils.escapeReservedCharactersHTML(e.getMessage())); 711 } 712 713 /** 714 * Explains an {@link Exception} to the user. 715 * 716 * @param e the {@link Exception} 717 * @return The HTML formatted error message to display 718 */ 719 public static String explainException(Exception e) { 720 Logging.error(e); 721 if (e instanceof OsmTransferException) { 722 return explainOsmTransferException((OsmTransferException) e); 723 } else { 724 return explainGeneric(e); 725 } 726 } 727 728 static String getUrlFromException(OsmApiException e) { 729 if (e.getAccessedUrl() != null) { 730 try { 731 return new URL(e.getAccessedUrl()).getHost(); 732 } catch (MalformedURLException e1) { 733 Logging.warn(e1); 734 } 735 } 736 if (e.getUrl() != null) { 737 return e.getUrl(); 738 } else { 739 return OsmApi.getOsmApi().getBaseUrl(); 740 } 741 } 742}