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.ExceptionUtil;
028import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
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 message.
357     *
358     * @param e the exception
359     */
360    public static void explainGenericHttpException(OsmApiException e) {
361        HelpAwareOptionPane.showOptionDialog(
362                Main.parent,
363                ExceptionUtil.explainClientTimeout(e),
364                tr("Communication with OSM server failed"),
365                JOptionPane.ERROR_MESSAGE,
366                ht("/ErrorMessages#GenericCommunicationError")
367        );
368    }
369
370    /**
371     * Explains a {@link OsmApiException} which was thrown because accessing a protected
372     * resource was forbidden.
373     *
374     * @param e the exception
375     */
376    public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
377        HelpAwareOptionPane.showOptionDialog(
378                Main.parent,
379                ExceptionUtil.explainMissingOAuthAccessTokenException(e),
380                tr("Authentication failed"),
381                JOptionPane.ERROR_MESSAGE,
382                ht("/ErrorMessages#MissingOAuthAccessToken")
383        );
384    }
385
386    /**
387     * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
388     * This is most likely happening when there is an error in the API URL or when
389     * local DNS services are not working.
390     *
391     * @param e the exception
392     */
393    public static void explainNestedUnkonwnHostException(OsmTransferException e) {
394        HelpAwareOptionPane.showOptionDialog(
395                Main.parent,
396                ExceptionUtil.explainNestedUnknownHostException(e),
397                tr("Unknown host"),
398                JOptionPane.ERROR_MESSAGE,
399                ht("/ErrorMessages#UnknownHost")
400        );
401    }
402
403    /**
404     * Explains an {@link OsmTransferException} to the user.
405     *
406     * @param e the {@link OsmTransferException}
407     */
408    public static void explainOsmTransferException(OsmTransferException e) {
409        if (ExceptionUtil.getNestedException(e, SecurityException.class) != null) {
410            explainSecurityException(e);
411            return;
412        }
413        if (ExceptionUtil.getNestedException(e, SocketException.class) != null) {
414            explainNestedSocketException(e);
415            return;
416        }
417        if (ExceptionUtil.getNestedException(e, UnknownHostException.class) != null) {
418            explainNestedUnkonwnHostException(e);
419            return;
420        }
421        if (ExceptionUtil.getNestedException(e, IOException.class) != null) {
422            explainNestedIOException(e);
423            return;
424        }
425        if (ExceptionUtil.getNestedException(e, IllegalDataException.class) != null) {
426            explainNestedIllegalDataException(e);
427            return;
428        }
429        if (ExceptionUtil.getNestedException(e, OfflineAccessException.class) != null) {
430            explainNestedOfflineAccessException(e);
431            return;
432        }
433        if (e instanceof OsmApiInitializationException) {
434            explainOsmApiInitializationException((OsmApiInitializationException) e);
435            return;
436        }
437
438        if (e instanceof ChangesetClosedException) {
439            explainChangesetClosedException((ChangesetClosedException) e);
440            return;
441        }
442
443        if (e instanceof MissingOAuthAccessTokenException) {
444            explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException) e);
445            return;
446        }
447
448        if (e instanceof OsmApiException) {
449            OsmApiException oae = (OsmApiException) e;
450            switch(oae.getResponseCode()) {
451            case HttpURLConnection.HTTP_PRECON_FAILED:
452                explainPreconditionFailed(oae);
453                return;
454            case HttpURLConnection.HTTP_GONE:
455                explainGoneForUnknownPrimitive(oae);
456                return;
457            case HttpURLConnection.HTTP_INTERNAL_ERROR:
458                explainInternalServerError(oae);
459                return;
460            case HttpURLConnection.HTTP_BAD_REQUEST:
461                explainBadRequest(oae);
462                return;
463            case HttpURLConnection.HTTP_NOT_FOUND:
464                explainNotFound(oae);
465                return;
466            case HttpURLConnection.HTTP_CONFLICT:
467                explainConflict(oae);
468                return;
469            case HttpURLConnection.HTTP_UNAUTHORIZED:
470                explainAuthenticationFailed(oae);
471                return;
472            case HttpURLConnection.HTTP_FORBIDDEN:
473                explainAuthorizationFailed(oae);
474                return;
475            case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
476                explainClientTimeout(oae);
477                return;
478            case 509:
479                explainBandwidthLimitExceeded(oae);
480                return;
481            default:
482                explainGenericHttpException(oae);
483                return;
484            }
485        }
486        explainGeneric(e);
487    }
488
489    /**
490     * explains the case of an error due to a delete request on an already deleted
491     * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
492     * {@link OsmPrimitive} is causing the error.
493     *
494     * @param e the exception
495     */
496    public static void explainGoneForUnknownPrimitive(OsmApiException e) {
497        HelpAwareOptionPane.showOptionDialog(
498                Main.parent,
499                ExceptionUtil.explainGoneForUnknownPrimitive(e),
500                tr("Object deleted"),
501                JOptionPane.ERROR_MESSAGE,
502                ht("/ErrorMessages#GoneForUnknownPrimitive")
503        );
504    }
505
506    /**
507     * Explains an {@link Exception} to the user.
508     *
509     * @param e the {@link Exception}
510     */
511    public static void explainException(Exception e) {
512        if (ExceptionUtil.getNestedException(e, InvocationTargetException.class) != null) {
513            explainNestedInvocationTargetException(e);
514            return;
515        }
516        if (e instanceof OsmTransferException) {
517            explainOsmTransferException((OsmTransferException) e);
518            return;
519        }
520        explainGeneric(e);
521    }
522}