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