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