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            BugReportExceptionHandler.handleException(ex);
197        }
198    }
199
200    /**
201     * Explains a {@link OsmApiException} which was thrown because of an internal server
202     * error in the OSM API server.
203     *
204     * @param e the exception
205     */
206
207    public static void explainInternalServerError(OsmTransferException e) {
208        HelpAwareOptionPane.showOptionDialog(
209                Main.parent,
210                ExceptionUtil.explainInternalServerError(e),
211                tr("Internal Server Error"),
212                JOptionPane.ERROR_MESSAGE,
213                ht("/ErrorMessages#InternalServerError")
214        );
215    }
216
217    /**
218     * Explains a {@link OsmApiException} which was thrown because of a bad
219     * request
220     *
221     * @param e the exception
222     */
223    public static void explainBadRequest(OsmApiException e) {
224        HelpAwareOptionPane.showOptionDialog(
225                Main.parent,
226                ExceptionUtil.explainBadRequest(e),
227                tr("Bad Request"),
228                JOptionPane.ERROR_MESSAGE,
229                ht("/ErrorMessages#BadRequest")
230        );
231    }
232
233    /**
234     * Explains a {@link OsmApiException} which was thrown because a resource wasn't found
235     * on the server
236     *
237     * @param e the exception
238     */
239    public static void explainNotFound(OsmApiException e) {
240        HelpAwareOptionPane.showOptionDialog(
241                Main.parent,
242                ExceptionUtil.explainNotFound(e),
243                tr("Not Found"),
244                JOptionPane.ERROR_MESSAGE,
245                ht("/ErrorMessages#NotFound")
246        );
247    }
248
249    /**
250     * Explains a {@link OsmApiException} which was thrown because of a conflict
251     *
252     * @param e the exception
253     */
254    public static void explainConflict(OsmApiException e) {
255        HelpAwareOptionPane.showOptionDialog(
256                Main.parent,
257                ExceptionUtil.explainConflict(e),
258                tr("Conflict"),
259                JOptionPane.ERROR_MESSAGE,
260                ht("/ErrorMessages#Conflict")
261        );
262    }
263
264    /**
265     * Explains a {@link OsmApiException} which was thrown because the authentication at
266     * the OSM server failed
267     *
268     * @param e the exception
269     */
270    public static void explainAuthenticationFailed(OsmApiException e) {
271        String msg;
272        if (OsmApi.isUsingOAuth()) {
273            msg = ExceptionUtil.explainFailedOAuthAuthentication(e);
274        } else {
275            msg = ExceptionUtil.explainFailedBasicAuthentication(e);
276        }
277
278        HelpAwareOptionPane.showOptionDialog(
279                Main.parent,
280                msg,
281                tr("Authentication failed"),
282                JOptionPane.ERROR_MESSAGE,
283                ht("/ErrorMessages#AuthenticationFailed")
284        );
285    }
286
287    /**
288     * Explains a {@link OsmApiException} which was thrown because accessing a protected
289     * resource was forbidden (HTTP 403).
290     *
291     * @param e the exception
292     */
293    public static void explainAuthorizationFailed(OsmApiException e) {
294
295        Matcher m;
296        String msg;
297        String url = e.getAccessedUrl();
298        Pattern p = Pattern.compile("https?://.*/api/0.6/(node|way|relation)/(\\d+)/(\\d+)");
299
300        // Special case for individual access to redacted versions
301        // See http://wiki.openstreetmap.org/wiki/Open_Database_License/Changes_in_the_API
302        if (url != null && (m = p.matcher(url)).matches()) {
303            String type = m.group(1);
304            String id = m.group(2);
305            String version = m.group(3);
306            // {1} is the translation of "node", "way" or "relation"
307            msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.",
308                    version, tr(type), id);
309        } else if (OsmApi.isUsingOAuth()) {
310            msg = ExceptionUtil.explainFailedOAuthAuthorisation(e);
311        } else {
312            msg = ExceptionUtil.explainFailedAuthorisation(e);
313        }
314
315        HelpAwareOptionPane.showOptionDialog(
316                Main.parent,
317                msg,
318                tr("Authorisation Failed"),
319                JOptionPane.ERROR_MESSAGE,
320                ht("/ErrorMessages#AuthorizationFailed")
321        );
322    }
323
324    /**
325     * Explains a {@link OsmApiException} which was thrown because of a
326     * client timeout (HTTP 408)
327     *
328     * @param e the exception
329     */
330    public static void explainClientTimeout(OsmApiException e) {
331        HelpAwareOptionPane.showOptionDialog(
332                Main.parent,
333                ExceptionUtil.explainClientTimeout(e),
334                tr("Client Time Out"),
335                JOptionPane.ERROR_MESSAGE,
336                ht("/ErrorMessages#ClientTimeOut")
337        );
338    }
339
340    /**
341     * Explains a {@link OsmApiException} which was thrown because of a
342     * bandwidth limit (HTTP 509)
343     *
344     * @param e the exception
345     */
346    public static void explainBandwidthLimitExceeded(OsmApiException e) {
347        HelpAwareOptionPane.showOptionDialog(
348                Main.parent,
349                ExceptionUtil.explainBandwidthLimitExceeded(e),
350                tr("Bandwidth Limit Exceeded"),
351                JOptionPane.ERROR_MESSAGE,
352                ht("/ErrorMessages#BandwidthLimit")
353        );
354    }
355
356    /**
357     * Explains a {@link OsmApiException} with a generic error message.
358     *
359     * @param e the exception
360     */
361    public static void explainGenericHttpException(OsmApiException e) {
362        String body = e.getErrorBody();
363        Object msg = null;
364        if ("text/html".equals(e.getContentType()) && body != null && body.startsWith("<") && body.contains("<html>")) {
365            msg = new HtmlPanel(body);
366        } else {
367            msg = ExceptionUtil.explainGeneric(e);
368        }
369        HelpAwareOptionPane.showOptionDialog(
370                Main.parent,
371                msg,
372                tr("Communication with OSM server failed"),
373                JOptionPane.ERROR_MESSAGE,
374                ht("/ErrorMessages#GenericCommunicationError")
375        );
376    }
377
378    /**
379     * Explains a {@link OsmApiException} which was thrown because accessing a protected
380     * resource was forbidden.
381     *
382     * @param e the exception
383     */
384    public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
385        HelpAwareOptionPane.showOptionDialog(
386                Main.parent,
387                ExceptionUtil.explainMissingOAuthAccessTokenException(e),
388                tr("Authentication failed"),
389                JOptionPane.ERROR_MESSAGE,
390                ht("/ErrorMessages#MissingOAuthAccessToken")
391        );
392    }
393
394    /**
395     * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
396     * This is most likely happening when there is an error in the API URL or when
397     * local DNS services are not working.
398     *
399     * @param e the exception
400     */
401    public static void explainNestedUnkonwnHostException(OsmTransferException e) {
402        HelpAwareOptionPane.showOptionDialog(
403                Main.parent,
404                ExceptionUtil.explainNestedUnknownHostException(e),
405                tr("Unknown host"),
406                JOptionPane.ERROR_MESSAGE,
407                ht("/ErrorMessages#UnknownHost")
408        );
409    }
410
411    /**
412     * Explains an {@link OsmTransferException} to the user.
413     *
414     * @param e the {@link OsmTransferException}
415     */
416    public static void explainOsmTransferException(OsmTransferException e) {
417        if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) {
418            explainSecurityException(e);
419            return;
420        }
421        if (ExceptionUtil.getNestedException(e, SocketException.class) != null) {
422            explainNestedSocketException(e);
423            return;
424        }
425        if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) {
426            explainNestedUnkonwnHostException(e);
427            return;
428        }
429        if (ExceptionUtil.getNestedException(e, IOException.class) != null) {
430            explainNestedIOException(e);
431            return;
432        }
433        if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) {
434            explainNestedIllegalDataException(e);
435            return;
436        }
437        if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) {
438            explainNestedOfflineAccessException(e);
439            return;
440        }
441        if (e instanceof OsmApiInitializationException) {
442            explainOsmApiInitializationException((OsmApiInitializationException) e);
443            return;
444        }
445
446        if (e instanceof ChangesetClosedException) {
447            explainChangesetClosedException((ChangesetClosedException) e);
448            return;
449        }
450
451        if (e instanceof MissingOAuthAccessTokenException) {
452            explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e);
453            return;
454        }
455
456        if (e instanceof OsmApiException) {
457            OsmApiException oae = (OsmApiException) e;
458            switch(oae.getResponseCode()) {
459            case HttpURLConnection.HTTP_PRECON_FAILED:
460                explainPreconditionFailed(oae);
461                return;
462            case HttpURLConnection.HTTP_GONE:
463                explainGoneForUnknownPrimitive(oae);
464                return;
465            case HttpURLConnection.HTTP_INTERNAL_ERROR:
466                explainInternalServerError(oae);
467                return;
468            case HttpURLConnection.HTTP_BAD_REQUEST:
469                explainBadRequest(oae);
470                return;
471            case HttpURLConnection.HTTP_NOT_FOUND:
472                explainNotFound(oae);
473                return;
474            case HttpURLConnection.HTTP_CONFLICT:
475                explainConflict(oae);
476                return;
477            case HttpURLConnection.HTTP_UNAUTHORIZED:
478                explainAuthenticationFailed(oae);
479                return;
480            case HttpURLConnection.HTTP_FORBIDDEN:
481                explainAuthorizationFailed(oae);
482                return;
483            case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
484                explainClientTimeout(oae);
485                return;
486            case 509: case 429:
487                explainBandwidthLimitExceeded(oae);
488                return;
489            default:
490                explainGenericHttpException(oae);
491                return;
492            }
493        }
494        explainGeneric(e);
495    }
496
497    /**
498     * explains the case of an error due to a delete request on an already deleted
499     * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
500     * {@link OsmPrimitive} is causing the error.
501     *
502     * @param e the exception
503     */
504    public static void explainGoneForUnknownPrimitive(OsmApiException e) {
505        HelpAwareOptionPane.showOptionDialog(
506                Main.parent,
507                ExceptionUtil.explainGoneForUnknownPrimitive(e),
508                tr("Object deleted"),
509                JOptionPane.ERROR_MESSAGE,
510                ht("/ErrorMessages#GoneForUnknownPrimitive")
511        );
512    }
513
514    /**
515     * Explains an {@link Exception} to the user.
516     *
517     * @param e the {@link Exception}
518     */
519    public static void explainException(Exception e) {
520        if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) {
521            explainNestedInvocationTargetException(e);
522            return;
523        }
524        if (e instanceof OsmTransferException) {
525            explainOsmTransferException((OsmTransferException) e);
526            return;
527        }
528        explainGeneric(e);
529    }
530}