// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui;
import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.io.ChangesetClosedException;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
import org.openstreetmap.josm.io.OsmApi;
import org.openstreetmap.josm.io.OsmApiException;
import org.openstreetmap.josm.io.OsmApiInitializationException;
import org.openstreetmap.josm.io.OsmTransferException;
import org.openstreetmap.josm.tools.BugReportExceptionHandler;
import org.openstreetmap.josm.tools.ExceptionUtil;
/**
* This utility class provides static methods which explain various exceptions to the user.
*
*/
public final class ExceptionDialogUtil {
/**
* just static utility functions. no constructor
*/
private ExceptionDialogUtil() {
}
/**
* handles an exception caught during OSM API initialization
*
* @param e the exception
*/
public static void explainOsmApiInitializationException(OsmApiInitializationException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainOsmApiInitializationException(e),
tr("Error"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#OsmApiInitializationException")
);
}
/**
* handles a ChangesetClosedException
*
* @param e the exception
*/
public static void explainChangesetClosedException(ChangesetClosedException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainChangesetClosedException(e),
tr("Error"),
JOptionPane.ERROR_MESSAGE,
ht("/Action/Upload#ChangesetClosed")
);
}
/**
* Explains an upload error due to a violated precondition, i.e. a HTTP return code 412
*
* @param e the exception
*/
public static void explainPreconditionFailed(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainPreconditionFailed(e),
tr("Precondition violation"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#OsmApiException")
);
}
/**
* Explains an exception with a generic message dialog
*
* @param e the exception
*/
public static void explainGeneric(Exception e) {
Main.error(e);
BugReportExceptionHandler.handleException(e);
}
/**
* Explains a {@link SecurityException} which has caused an {@link OsmTransferException}.
* This is most likely happening when user tries to access the OSM API from within an
* applet which wasn't loaded from the API server.
*
* @param e the exception
*/
public static void explainSecurityException(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainSecurityException(e),
tr("Security exception"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#SecurityException")
);
}
/**
* Explains a {@link SocketException} which has caused an {@link OsmTransferException}.
* This is most likely because there's not connection to the Internet or because
* the remote server is not reachable.
*
* @param e the exception
*/
public static void explainNestedSocketException(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainNestedSocketException(e),
tr("Network exception"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#NestedSocketException")
);
}
/**
* Explains a {@link IOException} which has caused an {@link OsmTransferException}.
* This is most likely happening when the communication with the remote server is
* interrupted for any reason.
*
* @param e the exception
*/
public static void explainNestedIOException(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainNestedIOException(e),
tr("IO Exception"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#NestedIOException")
);
}
/**
* Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}.
* This is most likely happening when JOSM tries to load data in an unsupported format.
*
* @param e the exception
*/
public static void explainNestedIllegalDataException(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainNestedIllegalDataException(e),
tr("Illegal Data"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#IllegalDataException")
);
}
/**
* Explains a {@link InvocationTargetException }
*
* @param e the exception
*/
public static void explainNestedInvocationTargetException(Exception e) {
InvocationTargetException ex = getNestedException(e, InvocationTargetException.class);
if (ex != null) {
// Users should be able to submit a bug report for an invocation target exception
//
BugReportExceptionHandler.handleException(ex);
return;
}
}
/**
* Explains a {@link OsmApiException} which was thrown because of an internal server
* error in the OSM API server.
*
* @param e the exception
*/
public static void explainInternalServerError(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainInternalServerError(e),
tr("Internal Server Error"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#InternalServerError")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because of a bad
* request
*
* @param e the exception
*/
public static void explainBadRequest(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainBadRequest(e),
tr("Bad Request"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#BadRequest")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because a resource wasn't found
* on the server
*
* @param e the exception
*/
public static void explainNotFound(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainNotFound(e),
tr("Not Found"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#NotFound")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because of a conflict
*
* @param e the exception
*/
public static void explainConflict(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainConflict(e),
tr("Conflict"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#Conflict")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because the authentication at
* the OSM server failed
*
* @param e the exception
*/
public static void explainAuthenticationFailed(OsmApiException e) {
String msg;
if (OsmApi.isUsingOAuth()) {
msg = ExceptionUtil.explainFailedOAuthAuthentication(e);
} else {
msg = ExceptionUtil.explainFailedBasicAuthentication(e);
}
HelpAwareOptionPane.showOptionDialog(
Main.parent,
msg,
tr("Authentication Failed"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#AuthenticationFailed")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because accessing a protected
* resource was forbidden (HTTP 403).
*
* @param e the exception
*/
public static void explainAuthorizationFailed(OsmApiException e) {
Matcher m;
String msg;
String url = e.getAccessedUrl();
Pattern p = Pattern.compile("https?://.*/api/0.6/(node|way|relation)/(\\d+)/(\\d+)");
// Special case for individual access to redacted versions
// See http://wiki.openstreetmap.org/wiki/Open_Database_License/Changes_in_the_API
if (url != null && (m = p.matcher(url)).matches()) {
String type = m.group(1);
String id = m.group(2);
String version = m.group(3);
// {1} is the translation of "node", "way" or "relation"
msg = tr("Access to redacted version ''{0}'' of {1} {2} is forbidden.",
version, tr(type), id);
} else if (OsmApi.isUsingOAuth()) {
msg = ExceptionUtil.explainFailedOAuthAuthorisation(e);
} else {
msg = ExceptionUtil.explainFailedAuthorisation(e);
}
HelpAwareOptionPane.showOptionDialog(
Main.parent,
msg,
tr("Authorisation Failed"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#AuthorizationFailed")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because of a
* client timeout (HTTP 408)
*
* @param e the exception
*/
public static void explainClientTimeout(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainClientTimeout(e),
tr("Client Time Out"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#ClientTimeOut")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because of a
* bandwidth limit (HTTP 509)
*
* @param e the exception
*/
public static void explainBandwidthLimitExceeded(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainBandwidthLimitExceeded(e),
tr("Bandwidth Limit Exceeded"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#BandwidthLimit")
);
}
/**
* Explains a {@link OsmApiException} with a generic error
* message.
*
* @param e the exception
*/
public static void explainGenericHttpException(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainClientTimeout(e),
tr("Communication with OSM server failed"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#GenericCommunicationError")
);
}
/**
* Explains a {@link OsmApiException} which was thrown because accessing a protected
* resource was forbidden.
*
* @param e the exception
*/
public static void explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainMissingOAuthAccessTokenException(e),
tr("Authentication failed"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#MissingOAuthAccessToken")
);
}
/**
* Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
* This is most likely happening when there is an error in the API URL or when
* local DNS services are not working.
*
* @param e the exception
*/
public static void explainNestedUnkonwnHostException(OsmTransferException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainNestedUnknownHostException(e),
tr("Unknown host"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#UnknownHost")
);
}
/**
* Replies the first nested exception of type <code>nestedClass</code> (including
* the root exception <code>e</code>) or null, if no such exception is found.
*
* @param <T>
* @param e the root exception
* @param nestedClass the type of the nested exception
* @return the first nested exception of type <code>nestedClass</code> (including
* the root exception <code>e</code>) or null, if no such exception is found.
*/
protected static <T> T getNestedException(Exception e, Class<T> nestedClass) {
Throwable t = e;
while (t != null && !(nestedClass.isInstance(t))) {
t = t.getCause();
}
if (t == null)
return null;
else if (nestedClass.isInstance(t))
return nestedClass.cast(t);
return null;
}
/**
* Explains an {@link OsmTransferException} to the user.
*
* @param e the {@link OsmTransferException}
*/
public static void explainOsmTransferException(OsmTransferException e) {
if (getNestedException(e, SecurityException.class) != null) {
explainSecurityException(e);
return;
}
if (getNestedException(e, SocketException.class) != null) {
explainNestedSocketException(e);
return;
}
if (getNestedException(e, UnknownHostException.class) != null) {
explainNestedUnkonwnHostException(e);
return;
}
if (getNestedException(e, IOException.class) != null) {
explainNestedIOException(e);
return;
}
if (getNestedException(e, IllegalDataException.class) != null) {
explainNestedIllegalDataException(e);
return;
}
if (e instanceof OsmApiInitializationException) {
explainOsmApiInitializationException((OsmApiInitializationException) e);
return;
}
if (e instanceof ChangesetClosedException) {
explainChangesetClosedException((ChangesetClosedException)e);
return;
}
if (e instanceof MissingOAuthAccessTokenException) {
explainMissingOAuthAccessTokenException((MissingOAuthAccessTokenException)e);
return;
}
if (e instanceof OsmApiException) {
OsmApiException oae = (OsmApiException) e;
switch(oae.getResponseCode()) {
case HttpURLConnection.HTTP_PRECON_FAILED:
explainPreconditionFailed(oae);
return;
case HttpURLConnection.HTTP_GONE:
explainGoneForUnknownPrimitive(oae);
return;
case HttpURLConnection.HTTP_INTERNAL_ERROR:
explainInternalServerError(oae);
return;
case HttpURLConnection.HTTP_BAD_REQUEST:
explainBadRequest(oae);
return;
case HttpURLConnection.HTTP_NOT_FOUND:
explainNotFound(oae);
return;
case HttpURLConnection.HTTP_CONFLICT:
explainConflict(oae);
return;
case HttpURLConnection.HTTP_UNAUTHORIZED:
explainAuthenticationFailed(oae);
return;
case HttpURLConnection.HTTP_FORBIDDEN:
explainAuthorizationFailed(oae);
return;
case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
explainClientTimeout(oae);
return;
case 509:
explainBandwidthLimitExceeded(oae);
return;
default:
explainGenericHttpException(oae);
return;
}
}
explainGeneric(e);
}
/**
* explains the case of an error due to a delete request on an already deleted
* {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
* {@link OsmPrimitive} is causing the error.
*
* @param e the exception
*/
public static void explainGoneForUnknownPrimitive(OsmApiException e) {
HelpAwareOptionPane.showOptionDialog(
Main.parent,
ExceptionUtil.explainGoneForUnknownPrimitive(e),
tr("Object deleted"),
JOptionPane.ERROR_MESSAGE,
ht("/ErrorMessages#GoneForUnknownPrimitive")
);
}
/**
* Explains an {@link Exception} to the user.
*
* @param e the {@link Exception}
*/
public static void explainException(Exception e) {
if (getNestedException(e, InvocationTargetException.class) != null) {
explainNestedInvocationTargetException(e);
return;
}
if (e instanceof OsmTransferException) {
explainOsmTransferException((OsmTransferException) e);
return;
}
explainGeneric(e);
}
}