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