source: josm/trunk/src/org/openstreetmap/josm/tools/ExceptionUtil.java@ 12601

Last change on this file since 12601 was 12382, checked in by michael2402, 7 years ago

More documentation for the tools package

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