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