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

Last change on this file since 9949 was 9876, checked in by simon04, 8 years ago

fix #12521 - On Overpass API error OSM API gets blamed

  • 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.TreeSet;
18import java.util.regex.Matcher;
19import java.util.regex.Pattern;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
27import org.openstreetmap.josm.io.ChangesetClosedException;
28import org.openstreetmap.josm.io.IllegalDataException;
29import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
30import org.openstreetmap.josm.io.OfflineAccessException;
31import org.openstreetmap.josm.io.OsmApi;
32import org.openstreetmap.josm.io.OsmApiException;
33import org.openstreetmap.josm.io.OsmApiInitializationException;
34import org.openstreetmap.josm.io.OsmTransferException;
35import org.openstreetmap.josm.io.auth.CredentialsManager;
36import org.openstreetmap.josm.tools.date.DateUtils;
37
38/**
39 * Utilities for exception handling.
40 * @since 2097
41 */
42public final class ExceptionUtil {
43
44 private ExceptionUtil() {
45 // Hide default constructor for utils classes
46 }
47
48 /**
49 * Explains an exception caught during OSM API initialization.
50 *
51 * @param e the exception
52 * @return The HTML formatted error message to display
53 */
54 public static String explainOsmApiInitializationException(OsmApiInitializationException e) {
55 Main.error(e);
56 return tr(
57 "<html>Failed to initialize communication with the OSM server {0}.<br>"
58 + "Check the server URL in your preferences and your internet connection.",
59 OsmApi.getOsmApi().getServerUrl())+"</html>";
60 }
61
62 /**
63 * Explains a {@link OsmApiException} which was thrown because accessing a protected
64 * resource was forbidden.
65 *
66 * @param e the exception
67 * @return The HTML formatted error message to display
68 */
69 public static String explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
70 Main.error(e);
71 return tr(
72 "<html>Failed to authenticate at the OSM server ''{0}''.<br>"
73 + "You are using OAuth to authenticate but currently there is no<br>"
74 + "OAuth Access Token configured.<br>"
75 + "Please open the Preferences Dialog and generate or enter an Access Token."
76 + "</html>",
77 OsmApi.getOsmApi().getServerUrl()
78 );
79 }
80
81 public static Pair<OsmPrimitive, Collection<OsmPrimitive>> parsePreconditionFailed(String msg) {
82 if (msg == null)
83 return null;
84 final String ids = "(\\d+(?:,\\d+)*)";
85 final Collection<OsmPrimitive> refs = new TreeSet<>(); // error message can contain several times the same way
86 Matcher m;
87 m = Pattern.compile(".*Node (\\d+) is still used by relations? " + ids + ".*").matcher(msg);
88 if (m.matches()) {
89 OsmPrimitive n = new Node(Long.parseLong(m.group(1)));
90 for (String s : m.group(2).split(",")) {
91 refs.add(new Relation(Long.parseLong(s)));
92 }
93 return Pair.create(n, refs);
94 }
95 m = Pattern.compile(".*Node (\\d+) is still used by ways? " + ids + ".*").matcher(msg);
96 if (m.matches()) {
97 OsmPrimitive n = new Node(Long.parseLong(m.group(1)));
98 for (String s : m.group(2).split(",")) {
99 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}
Note: See TracBrowser for help on using the repository browser.