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

Last change on this file since 12992 was 12992, checked in by Don-vip, 9 months ago

fix #15435 - do not cache incorrect login credentials when using basic auth

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