001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.io.IOException;
008import java.lang.reflect.InvocationTargetException;
009import java.net.HttpURLConnection;
010import java.net.SocketException;
011import java.net.UnknownHostException;
012import java.util.regex.Matcher;
013import java.util.regex.Pattern;
014
015import javax.swing.JOptionPane;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.gui.widgets.HtmlPanel;
020import org.openstreetmap.josm.io.ChangesetClosedException;
021import org.openstreetmap.josm.io.IllegalDataException;
022import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
023import org.openstreetmap.josm.io.OfflineAccessException;
024import org.openstreetmap.josm.io.OsmApi;
025import org.openstreetmap.josm.io.OsmApiException;
026import org.openstreetmap.josm.io.OsmApiInitializationException;
027import org.openstreetmap.josm.io.OsmTransferException;
028import org.openstreetmap.josm.tools.ExceptionUtil;
029import org.openstreetmap.josm.tools.Logging;
030import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
031
032/**
033 * This utility class provides static methods which explain various exceptions to the user.
034 *
035 */
036public final class ExceptionDialogUtil {
037
038    /**
039     * just static utility functions. no constructor
040     */
041    private ExceptionDialogUtil() {
042        // Hide default constructor for utility classes
043    }
044
045    /**
046     * handles an exception caught during OSM API initialization
047     *
048     * @param e the exception
049     */
050    public static void explainOsmApiInitializationException(OsmApiInitializationException e) {
051        HelpAwareOptionPane.showOptionDialog(
052                Main.parent,
053                ExceptionUtil.explainOsmApiInitializationException(e),
054                tr("Error"),
055                JOptionPane.ERROR_MESSAGE,
056                ht("/ErrorMessages#OsmApiInitializationException")
057        );
058    }
059
060    /**
061     * handles a ChangesetClosedException
062     *
063     * @param e the exception
064     */
065    public static void explainChangesetClosedException(ChangesetClosedException e) {
066        HelpAwareOptionPane.showOptionDialog(
067                Main.parent,
068                ExceptionUtil.explainChangesetClosedException(e),
069                tr("Error"),
070                JOptionPane.ERROR_MESSAGE,
071                ht("/Action/Upload#ChangesetClosed")
072        );
073    }
074
075    /**
076     * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412
077     *
078     * @param e the exception
079     */
080    public static void explainPreconditionFailed(OsmApiException e) {
081        HelpAwareOptionPane.showOptionDialog(
082                Main.parent,
083                ExceptionUtil.explainPreconditionFailed(e),
084                tr("Precondition violation"),
085                JOptionPane.ERROR_MESSAGE,
086                ht("/ErrorMessages#OsmApiException")
087        );
088    }
089
090    /**
091     * Explains an exception with a generic message dialog
092     *
093     * @param e the exception
094     */
095    public static void explainGeneric(Exception e) {
096        Logging.error(e);
097        BugReportExceptionHandler.handleException(e);
098    }
099
100    /**
101     * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}.
102     * This is most likely happening when user tries to access the OSM API from within an
103     * applet which wasn't loaded from the API server.
104     *
105     * @param e the exception
106     */
107
108    public static void explainSecurityException(OsmTransferException e) {
109        HelpAwareOptionPane.showOptionDialog(
110                Main.parent,
111                ExceptionUtil.explainSecurityException(e),
112                tr("Security exception"),
113                JOptionPane.ERROR_MESSAGE,
114                ht("/ErrorMessages#SecurityException")
115        );
116    }
117
118    /**
119     * Explains a {@link SocketException} which has caused an {@link OsmTransferException}.
120     * This is most likely because there's not connection to the Internet or because
121     * the remote server is not reachable.
122     *
123     * @param e the exception
124     */
125
126    public static void explainNestedSocketException(OsmTransferException e) {
127        HelpAwareOptionPane.showOptionDialog(
128                Main.parent,
129                ExceptionUtil.explainNestedSocketException(e),
130                tr("Network exception"),
131                JOptionPane.ERROR_MESSAGE,
132                ht("/ErrorMessages#NestedSocketException")
133        );
134    }
135
136    /**
137     * Explains a {@link IOException} which has caused an {@link OsmTransferException}.
138     * This is most likely happening when the communication with the remote server is
139     * interrupted for any reason.
140     *
141     * @param e the exception
142     */
143
144    public static void explainNestedIOException(OsmTransferException e) {
145        HelpAwareOptionPane.showOptionDialog(
146                Main.parent,
147                ExceptionUtil.explainNestedIOException(e),
148                tr("IO Exception"),
149                JOptionPane.ERROR_MESSAGE,
150                ht("/ErrorMessages#NestedIOException")
151        );
152    }
153
154    /**
155     * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}.
156     * This is most likely happening when JOSM tries to load data in an unsupported format.
157     *
158     * @param e the exception
159     */
160    public static void explainNestedIllegalDataException(OsmTransferException e) {
161        HelpAwareOptionPane.showOptionDialog(
162                Main.parent,
163                ExceptionUtil.explainNestedIllegalDataException(e),
164                tr("Illegal Data"),
165                JOptionPane.ERROR_MESSAGE,
166                ht("/ErrorMessages#IllegalDataException")
167        );
168    }
169
170    /**
171     * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}.
172     * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode.
173     *
174     * @param e the exception
175     * @since 7434
176     */
177    public static void explainNestedOfflineAccessException(OsmTransferException e) {
178        HelpAwareOptionPane.showOptionDialog(
179                Main.parent,
180                ExceptionUtil.explainOfflineAccessException(e),
181                tr("Offline mode"),
182                JOptionPane.ERROR_MESSAGE,
183                ht("/ErrorMessages#OfflineAccessException")
184        );
185    }
186
187    /**
188     * Explains a {@link InvocationTargetException }
189     *
190     * @param e the exception
191     */
192    public static void explainNestedInvocationTargetException(Exception e) {
193        InvocationTargetException ex = ExceptionUtil.getNestedException(e, InvocationTargetException.class);
194        if (ex != null) {
195            // Users should be able to submit a bug report for an invocation target exception
196            //
197            BugReportExceptionHandler.handleException(ex);
198            return;
199        }
200    }
201
202    /**
203     * Explains a {@link OsmApiException} which was thrown because of an internal server
204     * error in the OSM API server.
205     *
206     * @param e the exception
207     */
208
209    public static void explainInternalServerError(OsmTransferException e) {
210        HelpAwareOptionPane.showOptionDialog(
211                Main.parent,
212                ExceptionUtil.explainInternalServerError(e),
213                tr("Internal Server Error"),
214                JOptionPane.ERROR_MESSAGE,
215                ht("/ErrorMessages#InternalServerError")
216        );
217    }
218
219    /**
220     * Explains a {@link OsmApiException} which was thrown because of a bad
221     * request
222     *
223     * @param e the exception
224     */
225    public static void explainBadRequest(OsmApiException e) {
226        HelpAwareOptionPane.showOptionDialog(
227                Main.parent,
228                ExceptionUtil.explainBadRequest(e),
229                tr("Bad Request"),
230                JOptionPane.ERROR_MESSAGE,
231                ht("/ErrorMessages#BadRequest")
232        );
233    }
234
235    /**
236     * Explains a {@link OsmApiException} which was thrown because a resource wasn't found
237     * on the server
238     *
239     * @param e the exception
240     */
241    public static void explainNotFound(OsmApiException e) {
242        HelpAwareOptionPane.showOptionDialog(
243                Main.parent,
244                ExceptionUtil.explainNotFound(e),
245                tr("Not Found"),
246                JOptionPane.ERROR_MESSAGE,
247                ht("/ErrorMessages#NotFound")
248        );
249    }
250
251    /**
252     * Explains a {@link OsmApiException} which was thrown because of a conflict
253     *
254     * @param e the exception
255     */
256    public static void explainConflict(OsmApiException e) {
257        HelpAwareOptionPane.showOptionDialog(
258                Main.parent,
259                ExceptionUtil.explainConflict(e),
260                tr("Conflict"),
261                JOptionPane.ERROR_MESSAGE,
262                ht("/ErrorMessages#Conflict")
263        );
264    }
265
266    /**
267     * Explains a {@link OsmApiException} which was thrown because the authentication at
268     * the OSM server failed
269     *
270     * @param e the exception
271     */
272    public static void explainAuthenticationFailed(OsmApiException e) {
273        String msg;
274        if (OsmApi.isUsingOAuth()) {
275            msg = ExceptionUtil.explainFailedOAuthAuthentication(e);
276        } else {
277            msg = ExceptionUtil.explainFailedBasicAuthentication(e);
278        }
279
280        HelpAwareOptionPane.showOptionDialog(
281                Main.parent,
282                msg,
283                tr("Authentication Failed"),
284                JOptionPane.ERROR_MESSAGE,
285                ht("/ErrorMessages#AuthenticationFailed")
286        );
287    }
288
289    /**
290     * Explains a {@link OsmApiException} which was thrown because accessing a protected
291     * resource was forbidden (HTTP 403).
292     *
293     * @param e the exception
294     */
295    public static void explainAuthorizationFailed(OsmApiException e) {
296
297        Matcher m;
298        String msg;
299        String url = e.getAccessedUrl();
300        Pattern p = Pattern.compile("https?://.*/api/0.6/(node|way|relation)/(\\d+)/(\\d+)");
301
302        // Special case for individual access to redacted versions
303        // See http://wiki.openstreetmap.org/wiki/Open_Database_License/Changes_in_the_API
304        if (url != null && (m = p.matcher(url)).matches()) {
305            String type = m.group(1);
306            String id = m.group(2);
307            String version = m.group(3);
308            // {1} is the translation of "node", "way" or "relation"
309            msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.",
310                    version, tr(type), id);
311        } else if (OsmApi.isUsingOAuth()) {
312            msg = ExceptionUtil.explainFailedOAuthAuthorisation(e);
313        } else {
314            msg = ExceptionUtil.explainFailedAuthorisation(e);
315        }
316
317        HelpAwareOptionPane.showOptionDialog(
318                Main.parent,
319                msg,
320                tr("Authorisation Failed"),
321                JOptionPane.ERROR_MESSAGE,
322                ht("/ErrorMessages#AuthorizationFailed")
323        );
324    }
325
326    /**
327     * Explains a {@link OsmApiException} which was thrown because of a
328     * client timeout (HTTP 408)
329     *
330     * @param e the exception
331     */
332    public static void explainClientTimeout(OsmApiException e) {
333        HelpAwareOptionPane.showOptionDialog(
334                Main.parent,
335                ExceptionUtil.explainClientTimeout(e),
336                tr("Client Time Out"),
337                JOptionPane.ERROR_MESSAGE,
338                ht("/ErrorMessages#ClientTimeOut")
339        );
340    }
341
342    /**
343     * Explains a {@link OsmApiException} which was thrown because of a
344     * bandwidth limit (HTTP 509)
345     *
346     * @param e the exception
347     */
348    public static void explainBandwidthLimitExceeded(OsmApiException e) {
349        HelpAwareOptionPane.showOptionDialog(
350                Main.parent,
351                ExceptionUtil.explainBandwidthLimitExceeded(e),
352                tr("Bandwidth Limit Exceeded"),
353                JOptionPane.ERROR_MESSAGE,
354                ht("/ErrorMessages#BandwidthLimit")
355        );
356    }
357
358    /**
359     * Explains a {@link OsmApiException} with a generic error message.
360     *
361     * @param e the exception
362     */
363    public static void explainGenericHttpException(OsmApiException e) {
364        String body = e.getErrorBody();
365        Object msg = null;
366        if ("text/html".equals(e.getContentType()) && body != null && body.startsWith("<") && body.contains("<html>")) {
367            msg = new HtmlPanel(body);
368        } else {
369            msg = ExceptionUtil.explainGeneric(e);
370        }
371        HelpAwareOptionPane.showOptionDialog(
372                Main.parent,
373                msg,
374                tr("Communication with OSM server failed"),
375                JOptionPane.ERROR_MESSAGE,
376                ht("/ErrorMessages#GenericCommunicationError")
377        );
378    }
379
380    /**
381     * Explains a {@link OsmApiException} which was thrown because accessing a protected
382     * resource was forbidden.
383     *
384     * @param e the exception
385     */
386    public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
387        HelpAwareOptionPane.showOptionDialog(
388                Main.parent,
389                ExceptionUtil.explainMissingOAuthAccessTokenException(e),
390                tr("Authentication failed"),
391                JOptionPane.ERROR_MESSAGE,
392                ht("/ErrorMessages#MissingOAuthAccessToken")
393        );
394    }
395
396    /**
397     * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
398     * This is most likely happening when there is an error in the API URL or when
399     * local DNS services are not working.
400     *
401     * @param e the exception
402     */
403    public static void explainNestedUnkonwnHostException(OsmTransferException e) {
404        HelpAwareOptionPane.showOptionDialog(
405                Main.parent,
406                ExceptionUtil.explainNestedUnknownHostException(e),
407                tr("Unknown host"),
408                JOptionPane.ERROR_MESSAGE,
409                ht("/ErrorMessages#UnknownHost")
410        );
411    }
412
413    /**
414     * Explains an {@link OsmTransferException} to the user.
415     *
416     * @param e the {@link OsmTransferException}
417     */
418    public static void explainOsmTransferException(OsmTransferException e) {
419        if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) {
420            explainSecurityException(e);
421            return;
422        }
423        if (ExceptionUtil.getNestedException(e, SocketException.class) != null) {
424            explainNestedSocketException(e);
425            return;
426        }
427        if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) {
428            explainNestedUnkonwnHostException(e);
429            return;
430        }
431        if (ExceptionUtil.getNestedException(e, IOException.class) != null) {
432            explainNestedIOException(e);
433            return;
434        }
435        if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) {
436            explainNestedIllegalDataException(e);
437            return;
438        }
439        if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) {
440            explainNestedOfflineAccessException(e);
441            return;
442        }
443        if (e instanceof OsmApiInitializationException) {
444            explainOsmApiInitializationException((OsmApiInitializationException) e);
445            return;
446        }
447
448        if (e instanceof ChangesetClosedException) {
449            explainChangesetClosedException((ChangesetClosedException) e);
450            return;
451        }
452
453        if (e instanceof MissingOAuthAccessTokenException) {
454            explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e);
455            return;
456        }
457
458        if (e instanceof OsmApiException) {
459            OsmApiException oae = (OsmApiException) e;
460            switch(oae.getResponseCode()) {
461            case HttpURLConnection.HTTP_PRECON_FAILED:
462                explainPreconditionFailed(oae);
463                return;
464            case HttpURLConnection.HTTP_GONE:
465                explainGoneForUnknownPrimitive(oae);
466                return;
467            case HttpURLConnection.HTTP_INTERNAL_ERROR:
468                explainInternalServerError(oae);
469                return;
470            case HttpURLConnection.HTTP_BAD_REQUEST:
471                explainBadRequest(oae);
472                return;
473            case HttpURLConnection.HTTP_NOT_FOUND:
474                explainNotFound(oae);
475                return;
476            case HttpURLConnection.HTTP_CONFLICT:
477                explainConflict(oae);
478                return;
479            case HttpURLConnection.HTTP_UNAUTHORIZED:
480                explainAuthenticationFailed(oae);
481                return;
482            case HttpURLConnection.HTTP_FORBIDDEN:
483                explainAuthorizationFailed(oae);
484                return;
485            case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
486                explainClientTimeout(oae);
487                return;
488            case 509: case 429:
489                explainBandwidthLimitExceeded(oae);
490                return;
491            default:
492                explainGenericHttpException(oae);
493                return;
494            }
495        }
496        explainGeneric(e);
497    }
498
499    /**
500     * explains the case of an error due to a delete request on an already deleted
501     * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
502     * {@link OsmPrimitive} is causing the error.
503     *
504     * @param e the exception
505     */
506    public static void explainGoneForUnknownPrimitive(OsmApiException e) {
507        HelpAwareOptionPane.showOptionDialog(
508                Main.parent,
509                ExceptionUtil.explainGoneForUnknownPrimitive(e),
510                tr("Object deleted"),
511                JOptionPane.ERROR_MESSAGE,
512                ht("/ErrorMessages#GoneForUnknownPrimitive")
513        );
514    }
515
516    /**
517     * Explains an {@link Exception} to the user.
518     *
519     * @param e the {@link Exception}
520     */
521    public static void explainException(Exception e) {
522        if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) {
523            explainNestedInvocationTargetException(e);
524            return;
525        }
526        if (e instanceof OsmTransferException) {
527            explainOsmTransferException((OsmTransferException) e);
528            return;
529        }
530        explainGeneric(e);
531    }
532}