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