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

Last change on this file since 15084 was 15084, checked in by Don-vip, 5 years ago

see #17722 - proper notification of user block

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