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

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

global use of !Utils.isEmpty/isBlank

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