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

Last change on this file was 19225, checked in by taylor.smock, 3 days ago

Fix #23925: Indicate/link to alternative download methods when the user attempts to download too much data

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