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