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, new Utils.Function<OsmPrimitive, Long>() { 144 145 @Override 146 public Long apply(OsmPrimitive x) { 147 return x.getId(); 148 } 149 }); 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 Main.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 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 Main.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 Main.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 Main.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 Main.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 Main.error(e); 370 String errMsg = e.getErrorHeader(); 371 if (errMsg == null) { 372 errMsg = e.getErrorBody(); 373 } 374 if (errMsg == null) { 375 errMsg = tr("no error message available"); 376 } 377 return tr("<html>" 378 + "Communication with the OSM server ''{0}''failed. The server replied<br>" 379 + "the following error code and the following error message:<br>" 380 + "<strong>Error code:<strong> {1}<br>" 381 + "<strong>Error message (untranslated)</strong>: {2}" 382 + "</html>", 383 getUrlFromException(e), 384 e.getResponseCode(), 385 errMsg 386 ); 387 } 388 389 /** 390 * Explains an error due to a 409 conflict 391 * 392 * @param e the exception 393 * @return The HTML formatted error message to display 394 */ 395 public static String explainConflict(OsmApiException e) { 396 Main.error(e); 397 String msg = e.getErrorHeader(); 398 if (msg != null) { 399 Matcher m = Pattern.compile("The changeset (\\d+) was closed at (.*)").matcher(msg); 400 if (m.matches()) { 401 long changesetId = Long.parseLong(m.group(1)); 402 Date closeDate = null; 403 try { 404 closeDate = DateUtils.newOsmApiDateTimeFormat().parse(m.group(2)); 405 } catch (ParseException ex) { 406 Main.error(tr("Failed to parse date ''{0}'' replied by server.", m.group(2))); 407 Main.error(ex); 408 } 409 if (closeDate == null) { 410 msg = tr( 411 "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.", 412 changesetId 413 ); 414 } else { 415 msg = tr( 416 "<html>Closing of changeset <strong>{0}</strong> failed<br>" 417 +" because it has already been closed on {1}.", 418 changesetId, 419 DateUtils.formatDateTime(closeDate, DateFormat.DEFAULT, DateFormat.DEFAULT) 420 ); 421 } 422 return msg; 423 } 424 msg = tr( 425 "<html>The server reported that it has detected a conflict.<br>" + 426 "Error message (untranslated):<br>{0}</html>", 427 msg 428 ); 429 } else { 430 msg = tr( 431 "<html>The server reported that it has detected a conflict."); 432 } 433 return msg.endsWith("</html>") ? msg : (msg + "</html>"); 434 } 435 436 /** 437 * Explains an exception thrown during upload because the changeset which data is 438 * uploaded to is already closed. 439 * 440 * @param e the exception 441 * @return The HTML formatted error message to display 442 */ 443 public static String explainChangesetClosedException(ChangesetClosedException e) { 444 Main.error(e); 445 return tr( 446 "<html>Failed to upload to changeset <strong>{0}</strong><br>" 447 +"because it has already been closed on {1}.", 448 e.getChangesetId(), 449 e.getClosedOn() == null ? "?" : DateUtils.formatDateTime(e.getClosedOn(), DateFormat.DEFAULT, DateFormat.DEFAULT) 450 ); 451 } 452 453 /** 454 * Explains an exception with a generic message dialog 455 * 456 * @param e the exception 457 * @return The HTML formatted error message to display 458 */ 459 public static String explainGeneric(Exception e) { 460 String msg = e.getMessage(); 461 if (msg == null || msg.trim().isEmpty()) { 462 msg = e.toString(); 463 } 464 Main.error(e); 465 return Utils.escapeReservedCharactersHTML(msg); 466 } 467 468 /** 469 * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}. 470 * This is most likely happening when user tries to access the OSM API from within an 471 * applet which wasn't loaded from the API server. 472 * 473 * @param e the exception 474 * @return The HTML formatted error message to display 475 */ 476 public static String explainSecurityException(OsmTransferException e) { 477 String apiUrl = e.getUrl(); 478 String host = tr("unknown"); 479 try { 480 host = new URL(apiUrl).getHost(); 481 } catch (MalformedURLException ex) { 482 // shouldn't happen 483 if (Main.isTraceEnabled()) { 484 Main.trace(e.getMessage()); 485 } 486 } 487 488 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>" 489 + "for security reasons. This is most likely because you are running<br>" 490 + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host)+"</html>"; 491 } 492 493 /** 494 * Explains a {@link SocketException} which has caused an {@link OsmTransferException}. 495 * This is most likely because there's not connection to the Internet or because 496 * the remote server is not reachable. 497 * 498 * @param e the exception 499 * @return The HTML formatted error message to display 500 */ 501 public static String explainNestedSocketException(OsmTransferException e) { 502 Main.error(e); 503 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 504 + "Please check your internet connection.", e.getUrl())+"</html>"; 505 } 506 507 /** 508 * Explains a {@link IOException} which has caused an {@link OsmTransferException}. 509 * This is most likely happening when the communication with the remote server is 510 * interrupted for any reason. 511 * 512 * @param e the exception 513 * @return The HTML formatted error message to display 514 */ 515 public static String explainNestedIOException(OsmTransferException e) { 516 IOException ioe = getNestedException(e, IOException.class); 517 Main.error(e); 518 return tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>" 519 + "due to a problem with transferring data.<br>" 520 + "Details (untranslated): {1}</html>", e.getUrl(), 521 ioe != null ? ioe.getMessage() : "null"); 522 } 523 524 /** 525 * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}. 526 * This is most likely happening when JOSM tries to load data in an unsupported format. 527 * 528 * @param e the exception 529 * @return The HTML formatted error message to display 530 */ 531 public static String explainNestedIllegalDataException(OsmTransferException e) { 532 IllegalDataException ide = getNestedException(e, IllegalDataException.class); 533 Main.error(e); 534 return tr("<html>Failed to download data. " 535 + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>" 536 + "<br>Details (untranslated): {0}</html>", ide != null ? ide.getMessage() : "null"); 537 } 538 539 /** 540 * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}. 541 * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode. 542 * 543 * @param e the exception 544 * @return The HTML formatted error message to display 545 * @since 7434 546 */ 547 public static String explainOfflineAccessException(OsmTransferException e) { 548 OfflineAccessException oae = getNestedException(e, OfflineAccessException.class); 549 Main.error(e); 550 return tr("<html>Failed to download data.<br>" 551 + "<br>Details: {0}</html>", oae != null ? oae.getMessage() : "null"); 552 } 553 554 /** 555 * Explains a {@link OsmApiException} which was thrown because of an internal server 556 * error in the OSM API server. 557 * 558 * @param e the exception 559 * @return The HTML formatted error message to display 560 */ 561 public static String explainInternalServerError(OsmTransferException e) { 562 Main.error(e); 563 return tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>" 564 + "This is most likely a temporary problem. Please try again later.", e.getUrl())+"</html>"; 565 } 566 567 /** 568 * Explains a {@link OsmApiException} which was thrown because of a bad request. 569 * 570 * @param e the exception 571 * @return The HTML formatted error message to display 572 */ 573 public static String explainBadRequest(OsmApiException e) { 574 String message = tr("The OSM server ''{0}'' reported a bad request.<br>", getUrlFromException(e)); 575 String errorHeader = e.getErrorHeader(); 576 if (errorHeader != null && (errorHeader.startsWith("The maximum bbox") || 577 errorHeader.startsWith("You requested too many nodes"))) { 578 message += "<br>" 579 + tr("The area you tried to download is too big or your request was too large." 580 + "<br>Either request a smaller area or use an export file provided by the OSM community."); 581 } else if (errorHeader != null) { 582 message += tr("<br>Error message(untranslated): {0}", errorHeader); 583 } 584 Main.error(e); 585 return "<html>" + message + "</html>"; 586 } 587 588 /** 589 * Explains a {@link OsmApiException} which was thrown because of 590 * bandwidth limit exceeded (HTTP error 509) 591 * 592 * @param e the exception 593 * @return The HTML formatted error message to display 594 */ 595 public static String explainBandwidthLimitExceeded(OsmApiException e) { 596 Main.error(e); 597 // TODO: Write a proper error message 598 return explainGenericOsmApiException(e); 599 } 600 601 /** 602 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found. 603 * 604 * @param e the exception 605 * @return The HTML formatted error message to display 606 */ 607 public static String explainNotFound(OsmApiException e) { 608 String message = tr("The OSM server ''{0}'' does not know about an object<br>" 609 + "you tried to read, update, or delete. Either the respective object<br>" 610 + "does not exist on the server or you are using an invalid URL to access<br>" 611 + "it. Please carefully check the server''s address ''{0}'' for typos.", 612 getUrlFromException(e)); 613 Main.error(e); 614 return "<html>" + message + "</html>"; 615 } 616 617 /** 618 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 619 * This is most likely happening when there is an error in the API URL or when 620 * local DNS services are not working. 621 * 622 * @param e the exception 623 * @return The HTML formatted error message to display 624 */ 625 public static String explainNestedUnknownHostException(OsmTransferException e) { 626 String apiUrl = e.getUrl(); 627 String host = tr("unknown"); 628 try { 629 host = new URL(apiUrl).getHost(); 630 } catch (MalformedURLException ex) { 631 // shouldn't happen 632 if (Main.isTraceEnabled()) { 633 Main.trace(e.getMessage()); 634 } 635 } 636 637 Main.error(e); 638 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 639 + "Host name ''{1}'' could not be resolved. <br>" 640 + "Please check the API URL in your preferences and your internet connection.", apiUrl, host)+"</html>"; 641 } 642 643 /** 644 * Replies the first nested exception of type <code>nestedClass</code> (including 645 * the root exception <code>e</code>) or null, if no such exception is found. 646 * 647 * @param <T> nested exception type 648 * @param e the root exception 649 * @param nestedClass the type of the nested exception 650 * @return the first nested exception of type <code>nestedClass</code> (including 651 * the root exception <code>e</code>) or null, if no such exception is found. 652 * @since 8470 653 */ 654 public static <T> T getNestedException(Exception e, Class<T> nestedClass) { 655 Throwable t = e; 656 while (t != null && !(nestedClass.isInstance(t))) { 657 t = t.getCause(); 658 } 659 if (t == null) 660 return null; 661 else if (nestedClass.isInstance(t)) 662 return nestedClass.cast(t); 663 return null; 664 } 665 666 /** 667 * Explains an {@link OsmTransferException} to the user. 668 * 669 * @param e the {@link OsmTransferException} 670 * @return The HTML formatted error message to display 671 */ 672 public static String explainOsmTransferException(OsmTransferException e) { 673 if (getNestedException(e, SecurityException.class) != null) 674 return explainSecurityException(e); 675 if (getNestedException(e, SocketException.class) != null) 676 return explainNestedSocketException(e); 677 if (getNestedException(e, UnknownHostException.class) != null) 678 return explainNestedUnknownHostException(e); 679 if (getNestedException(e, IOException.class) != null) 680 return explainNestedIOException(e); 681 if (e instanceof OsmApiInitializationException) 682 return explainOsmApiInitializationException((OsmApiInitializationException) e); 683 684 if (e instanceof ChangesetClosedException) 685 return explainChangesetClosedException((ChangesetClosedException) e); 686 687 if (e instanceof OsmApiException) { 688 OsmApiException oae = (OsmApiException) e; 689 if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) 690 return explainPreconditionFailed(oae); 691 if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE) 692 return explainGoneForUnknownPrimitive(oae); 693 if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) 694 return explainInternalServerError(oae); 695 if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST) 696 return explainBadRequest(oae); 697 if (oae.getResponseCode() == 509) 698 return explainBandwidthLimitExceeded(oae); 699 } 700 return explainGeneric(e); 701 } 702 703 /** 704 * explains the case of an error due to a delete request on an already deleted 705 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 706 * {@link OsmPrimitive} is causing the error. 707 * 708 * @param e the exception 709 * @return The HTML formatted error message to display 710 */ 711 public static String explainGoneForUnknownPrimitive(OsmApiException e) { 712 return tr( 713 "<html>The server reports that an object is deleted.<br>" 714 + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> " 715 + "<strong>Downloading failed</strong> if you tried to download this object.<br>" 716 + "<br>" 717 + "The error message is:<br>" + "{0}" 718 + "</html>", Utils.escapeReservedCharactersHTML(e.getMessage())); 719 } 720 721 /** 722 * Explains an {@link Exception} to the user. 723 * 724 * @param e the {@link Exception} 725 * @return The HTML formatted error message to display 726 */ 727 public static String explainException(Exception e) { 728 Main.error(e); 729 if (e instanceof OsmTransferException) { 730 return explainOsmTransferException((OsmTransferException) e); 731 } else { 732 return explainGeneric(e); 733 } 734 } 735 736 static String getUrlFromException(OsmApiException e) { 737 if (e.getAccessedUrl() != null) { 738 try { 739 return new URL(e.getAccessedUrl()).getHost(); 740 } catch (MalformedURLException e1) { 741 Main.warn(e1); 742 } 743 } 744 if (e.getUrl() != null) { 745 return e.getUrl(); 746 } else { 747 return OsmApi.getOsmApi().getBaseUrl(); 748 } 749 } 750}