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

Last change on this file since 17534 was 16643, checked in by simon04, 4 years ago

see #19334 - https://errorprone.info/bugpattern/StringSplitter

  • 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.text.DateFormat;
14import java.text.ParseException;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Date;
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 (msg != null && !msg.isEmpty()) {
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 Date closeDate = null;
401 try {
402 closeDate = DateUtils.newOsmApiDateTimeFormat().parse(m.group(2));
403 } catch (ParseException 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 DateUtils.formatDateTime(closeDate, DateFormat.DEFAULT, DateFormat.DEFAULT)
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 ? "?" : DateUtils.formatDateTime(e.getClosedOn(), DateFormat.DEFAULT, DateFormat.DEFAULT)
448 );
449 }
450
451 /**
452 * Explains an exception with a generic message dialog
453 *
454 * @param e the exception
455 * @return The HTML formatted error message to display
456 */
457 public static String explainGeneric(Exception e) {
458 String msg = e.getMessage();
459 if (msg == null || msg.trim().isEmpty()) {
460 msg = e.toString();
461 }
462 Logging.error(e);
463 return Utils.escapeReservedCharactersHTML(msg);
464 }
465
466 /**
467 * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}.
468 * This is most likely happening when user tries to access the OSM API from within an
469 * applet which wasn't loaded from the API server.
470 *
471 * @param e the exception
472 * @return The HTML formatted error message to display
473 */
474 public static String explainSecurityException(OsmTransferException e) {
475 String apiUrl = e.getUrl();
476 String host = tr("unknown");
477 try {
478 host = new URL(apiUrl).getHost();
479 } catch (MalformedURLException ex) {
480 // shouldn't happen
481 Logging.trace(ex);
482 }
483
484 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>"
485 + "for security reasons. This is most likely because you are running<br>"
486 + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host)+"</html>";
487 }
488
489 /**
490 * Explains a {@link SocketException} which has caused an {@link OsmTransferException}.
491 * This is most likely because there's not connection to the Internet or because
492 * the remote server is not reachable.
493 *
494 * @param e the exception
495 * @return The HTML formatted error message to display
496 */
497 public static String explainNestedSocketException(OsmTransferException e) {
498 Logging.error(e);
499 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
500 + "Please check your internet connection.", e.getUrl())+"</html>";
501 }
502
503 /**
504 * Explains a {@link IOException} which has caused an {@link OsmTransferException}.
505 * This is most likely happening when the communication with the remote server is
506 * interrupted for any reason.
507 *
508 * @param e the exception
509 * @return The HTML formatted error message to display
510 */
511 public static String explainNestedIOException(OsmTransferException e) {
512 IOException ioe = getNestedException(e, IOException.class);
513 Logging.error(e);
514 return tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>"
515 + "due to a problem with transferring data.<br>"
516 + "Details (untranslated): {1}</html>",
517 e != null ? e.getUrl() : "null",
518 ioe != null ? ioe.getMessage() : "null");
519 }
520
521 /**
522 * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}.
523 * This is most likely happening when JOSM tries to load data in an unsupported format.
524 *
525 * @param e the exception
526 * @return The HTML formatted error message to display
527 */
528 public static String explainNestedIllegalDataException(OsmTransferException e) {
529 IllegalDataException ide = getNestedException(e, IllegalDataException.class);
530 Logging.error(e);
531 return tr("<html>Failed to download data. "
532 + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>"
533 + "<br>Details (untranslated): {0}</html>", ide != null ? ide.getMessage() : "null");
534 }
535
536 /**
537 * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}.
538 * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode.
539 *
540 * @param e the exception
541 * @return The HTML formatted error message to display
542 * @since 7434
543 */
544 public static String explainOfflineAccessException(OsmTransferException e) {
545 OfflineAccessException oae = getNestedException(e, OfflineAccessException.class);
546 Logging.error(e);
547 return tr("<html>Failed to download data.<br>"
548 + "<br>Details: {0}</html>", oae != null ? oae.getMessage() : "null");
549 }
550
551 /**
552 * Explains a {@link OsmApiException} which was thrown because of an internal server
553 * error in the OSM API server.
554 *
555 * @param e the exception
556 * @return The HTML formatted error message to display
557 */
558 public static String explainInternalServerError(OsmTransferException e) {
559 Logging.error(e);
560 return tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>"
561 + "This is most likely a temporary problem. Please try again later.", e.getUrl())+"</html>";
562 }
563
564 /**
565 * Explains a {@link OsmApiException} which was thrown because of a bad request.
566 *
567 * @param e the exception
568 * @return The HTML formatted error message to display
569 */
570 public static String explainBadRequest(OsmApiException e) {
571 String message = tr("The OSM server ''{0}'' reported a bad request.<br>", getUrlFromException(e));
572 String errorHeader = e.getErrorHeader();
573 if (errorHeader != null && (errorHeader.startsWith("The maximum bbox") ||
574 errorHeader.startsWith("You requested too many nodes"))) {
575 message += "<br>"
576 + tr("The area you tried to download is too big or your request was too large."
577 + "<br>Either request a smaller area or use an export file provided by the OSM community.");
578 } else if (errorHeader != null) {
579 message += tr("<br>Error message(untranslated): {0}", errorHeader);
580 }
581 Logging.error(e);
582 return "<html>" + message + "</html>";
583 }
584
585 /**
586 * Explains a {@link OsmApiException} which was thrown because of
587 * bandwidth limit exceeded (HTTP error 509)
588 *
589 * @param e the exception
590 * @return The HTML formatted error message to display
591 */
592 public static String explainBandwidthLimitExceeded(OsmApiException e) {
593 Logging.error(e);
594 // TODO: Write a proper error message
595 return explainGenericOsmApiException(e);
596 }
597
598 /**
599 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found.
600 *
601 * @param e the exception
602 * @return The HTML formatted error message to display
603 */
604 public static String explainNotFound(OsmApiException e) {
605 String message = tr("The OSM server ''{0}'' does not know about an object<br>"
606 + "you tried to read, update, or delete. Either the respective object<br>"
607 + "does not exist on the server or you are using an invalid URL to access<br>"
608 + "it. Please carefully check the server''s address ''{0}'' for typos.",
609 getUrlFromException(e));
610 Logging.error(e);
611 return "<html>" + message + "</html>";
612 }
613
614 /**
615 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
616 * This is most likely happening when there is an error in the API URL or when
617 * local DNS services are not working.
618 *
619 * @param e the exception
620 * @return The HTML formatted error message to display
621 */
622 public static String explainNestedUnknownHostException(OsmTransferException e) {
623 String apiUrl = e.getUrl();
624 String host = tr("unknown");
625 try {
626 host = new URL(apiUrl).getHost();
627 } catch (MalformedURLException ex) {
628 // shouldn't happen
629 Logging.trace(e);
630 }
631
632 Logging.error(e);
633 return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
634 + "Host name ''{1}'' could not be resolved. <br>"
635 + "Please check the API URL in your preferences and your internet connection.", apiUrl, host)+"</html>";
636 }
637
638 /**
639 * Replies the first nested exception of type <code>nestedClass</code> (including
640 * the root exception <code>e</code>) or null, if no such exception is found.
641 *
642 * @param <T> nested exception type
643 * @param e the root exception
644 * @param nestedClass the type of the nested exception
645 * @return the first nested exception of type <code>nestedClass</code> (including
646 * the root exception <code>e</code>) or null, if no such exception is found.
647 * @since 8470
648 */
649 public static <T> T getNestedException(Exception e, Class<T> nestedClass) {
650 Throwable t = e;
651 while (t != null && !nestedClass.isInstance(t)) {
652 t = t.getCause();
653 }
654 if (t == null)
655 return null;
656 else if (nestedClass.isInstance(t))
657 return nestedClass.cast(t);
658 return null;
659 }
660
661 /**
662 * Explains an {@link OsmTransferException} to the user.
663 *
664 * @param e the {@link OsmTransferException}
665 * @return The HTML formatted error message to display
666 */
667 public static String explainOsmTransferException(OsmTransferException e) {
668 Objects.requireNonNull(e, "e");
669 if (getNestedException(e, SecurityException.class) != null)
670 return explainSecurityException(e);
671 if (getNestedException(e, SocketException.class) != null)
672 return explainNestedSocketException(e);
673 if (getNestedException(e, UnknownHostException.class) != null)
674 return explainNestedUnknownHostException(e);
675 if (getNestedException(e, IOException.class) != null)
676 return explainNestedIOException(e);
677 if (e instanceof OsmApiInitializationException)
678 return explainOsmApiInitializationException((OsmApiInitializationException) e);
679
680 if (e instanceof ChangesetClosedException)
681 return explainChangesetClosedException((ChangesetClosedException) e);
682
683 if (e instanceof OsmApiException) {
684 OsmApiException oae = (OsmApiException) e;
685 if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED)
686 return explainPreconditionFailed(oae);
687 if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE)
688 return explainGoneForUnknownPrimitive(oae);
689 if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR)
690 return explainInternalServerError(oae);
691 if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST)
692 return explainBadRequest(oae);
693 if (oae.getResponseCode() == 509)
694 return explainBandwidthLimitExceeded(oae);
695 }
696 return explainGeneric(e);
697 }
698
699 /**
700 * explains the case of an error due to a delete request on an already deleted
701 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
702 * {@link OsmPrimitive} is causing the error.
703 *
704 * @param e the exception
705 * @return The HTML formatted error message to display
706 */
707 public static String explainGoneForUnknownPrimitive(OsmApiException e) {
708 return tr(
709 "<html>The server reports that an object is deleted.<br>"
710 + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> "
711 + "<strong>Downloading failed</strong> if you tried to download this object.<br>"
712 + "<br>"
713 + "The error message is:<br>" + "{0}"
714 + "</html>", Utils.escapeReservedCharactersHTML(e.getMessage()));
715 }
716
717 /**
718 * Explains an {@link Exception} to the user.
719 *
720 * @param e the {@link Exception}
721 * @return The HTML formatted error message to display
722 */
723 public static String explainException(Exception e) {
724 Logging.error(e);
725 if (e instanceof OsmTransferException) {
726 return explainOsmTransferException((OsmTransferException) e);
727 } else {
728 return explainGeneric(e);
729 }
730 }
731
732 /**
733 * Determines if the OSM API exception has been thrown because user has been blocked or suspended.
734 * @param e OSM API exception
735 * @return {@code true} if the OSM API exception has been thrown because user has been blocked or suspended
736 * @since 15084
737 */
738 public static boolean isUserBlocked(OsmApiException e) {
739 return OSM_API_BLOCK_MESSAGES.contains(e.getErrorHeader());
740 }
741
742 static String getUrlFromException(OsmApiException e) {
743 if (e.getAccessedUrl() != null) {
744 try {
745 return new URL(e.getAccessedUrl()).getHost();
746 } catch (MalformedURLException e1) {
747 Logging.warn(e1);
748 }
749 }
750 if (e.getUrl() != null) {
751 return e.getUrl();
752 } else {
753 return OsmApi.getOsmApi().getBaseUrl();
754 }
755 }
756}
Note: See TracBrowser for help on using the repository browser.