[2097] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.tools;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
[4691] | 5 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
[2097] | 6 |
|
---|
| 7 | import java.io.IOException;
|
---|
| 8 | import java.net.HttpURLConnection;
|
---|
| 9 | import java.net.MalformedURLException;
|
---|
| 10 | import java.net.SocketException;
|
---|
| 11 | import java.net.URL;
|
---|
| 12 | import java.net.UnknownHostException;
|
---|
[2289] | 13 | import java.text.DateFormat;
|
---|
| 14 | import java.text.ParseException;
|
---|
[4691] | 15 | import java.util.Collection;
|
---|
[2289] | 16 | import java.util.Date;
|
---|
[4691] | 17 | import java.util.TreeSet;
|
---|
[2246] | 18 | import java.util.regex.Matcher;
|
---|
| 19 | import java.util.regex.Pattern;
|
---|
[2097] | 20 |
|
---|
| 21 | import org.openstreetmap.josm.Main;
|
---|
[4816] | 22 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 23 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
| 24 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
| 25 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[2748] | 26 | import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
|
---|
[2480] | 27 | import org.openstreetmap.josm.io.ChangesetClosedException;
|
---|
[3069] | 28 | import org.openstreetmap.josm.io.IllegalDataException;
|
---|
[2862] | 29 | import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
|
---|
[7434] | 30 | import org.openstreetmap.josm.io.OfflineAccessException;
|
---|
[2097] | 31 | import org.openstreetmap.josm.io.OsmApi;
|
---|
| 32 | import org.openstreetmap.josm.io.OsmApiException;
|
---|
| 33 | import org.openstreetmap.josm.io.OsmApiInitializationException;
|
---|
| 34 | import org.openstreetmap.josm.io.OsmTransferException;
|
---|
[4263] | 35 | import org.openstreetmap.josm.io.auth.CredentialsManager;
|
---|
[7299] | 36 | import org.openstreetmap.josm.tools.date.DateUtils;
|
---|
[2097] | 37 |
|
---|
[9474] | 38 | /**
|
---|
| 39 | * Utilities for exception handling.
|
---|
| 40 | * @since 2097
|
---|
| 41 | */
|
---|
[6362] | 42 | public final class ExceptionUtil {
|
---|
[6830] | 43 |
|
---|
[2097] | 44 | private ExceptionUtil() {
|
---|
[6362] | 45 | // Hide default constructor for utils classes
|
---|
[2097] | 46 | }
|
---|
| 47 |
|
---|
| 48 | /**
|
---|
[9474] | 49 | * Explains an exception caught during OSM API initialization.
|
---|
[2097] | 50 | *
|
---|
| 51 | * @param e the exception
|
---|
[7205] | 52 | * @return The HTML formatted error message to display
|
---|
[2097] | 53 | */
|
---|
| 54 | public static String explainOsmApiInitializationException(OsmApiInitializationException e) {
|
---|
[6643] | 55 | Main.error(e);
|
---|
[7024] | 56 | return tr(
|
---|
[2097] | 57 | "<html>Failed to initialize communication with the OSM server {0}.<br>"
|
---|
[6582] | 58 | + "Check the server URL in your preferences and your internet connection.",
|
---|
[9474] | 59 | OsmApi.getOsmApi().getServerUrl())+"</html>";
|
---|
[2097] | 60 | }
|
---|
| 61 |
|
---|
[9474] | 62 | /**
|
---|
| 63 | * Explains a {@link OsmApiException} which was thrown because accessing a protected
|
---|
| 64 | * resource was forbidden.
|
---|
| 65 | *
|
---|
| 66 | * @param e the exception
|
---|
| 67 | * @return The HTML formatted error message to display
|
---|
| 68 | */
|
---|
[2862] | 69 | public static String explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
|
---|
[6643] | 70 | Main.error(e);
|
---|
[7024] | 71 | return tr(
|
---|
[2862] | 72 | "<html>Failed to authenticate at the OSM server ''{0}''.<br>"
|
---|
| 73 | + "You are using OAuth to authenticate but currently there is no<br>"
|
---|
| 74 | + "OAuth Access Token configured.<br>"
|
---|
| 75 | + "Please open the Preferences Dialog and generate or enter an Access Token."
|
---|
| 76 | + "</html>",
|
---|
[9353] | 77 | OsmApi.getOsmApi().getServerUrl()
|
---|
[2862] | 78 | );
|
---|
| 79 | }
|
---|
| 80 |
|
---|
[4816] | 81 | public static Pair<OsmPrimitive, Collection<OsmPrimitive>> parsePreconditionFailed(String msg) {
|
---|
[9474] | 82 | if (msg == null)
|
---|
| 83 | return null;
|
---|
[4816] | 84 | final String ids = "(\\d+(?:,\\d+)*)";
|
---|
[7005] | 85 | final Collection<OsmPrimitive> refs = new TreeSet<>(); // error message can contain several times the same way
|
---|
[4816] | 86 | Matcher m;
|
---|
[9474] | 87 | m = Pattern.compile(".*Node (\\d+) is still used by relations? " + ids + ".*").matcher(msg);
|
---|
[4816] | 88 | if (m.matches()) {
|
---|
| 89 | OsmPrimitive n = new Node(Long.parseLong(m.group(1)));
|
---|
| 90 | for (String s : m.group(2).split(",")) {
|
---|
[4821] | 91 | refs.add(new Relation(Long.parseLong(s)));
|
---|
[4816] | 92 | }
|
---|
| 93 | return Pair.create(n, refs);
|
---|
| 94 | }
|
---|
[9474] | 95 | m = Pattern.compile(".*Node (\\d+) is still used by ways? " + ids + ".*").matcher(msg);
|
---|
[4816] | 96 | if (m.matches()) {
|
---|
| 97 | OsmPrimitive n = new Node(Long.parseLong(m.group(1)));
|
---|
| 98 | for (String s : m.group(2).split(",")) {
|
---|
[4821] | 99 | refs.add(new Way(Long.parseLong(s)));
|
---|
[4816] | 100 | }
|
---|
| 101 | return Pair.create(n, refs);
|
---|
| 102 | }
|
---|
| 103 | m = Pattern.compile(".*The relation (\\d+) is used in relations? " + ids + ".*").matcher(msg);
|
---|
| 104 | if (m.matches()) {
|
---|
| 105 | OsmPrimitive n = new Relation(Long.parseLong(m.group(1)));
|
---|
| 106 | for (String s : m.group(2).split(",")) {
|
---|
[4821] | 107 | refs.add(new Relation(Long.parseLong(s)));
|
---|
[4816] | 108 | }
|
---|
| 109 | return Pair.create(n, refs);
|
---|
| 110 | }
|
---|
[9474] | 111 | m = Pattern.compile(".*Way (\\d+) is still used by relations? " + ids + ".*").matcher(msg);
|
---|
[4816] | 112 | if (m.matches()) {
|
---|
| 113 | OsmPrimitive n = new Way(Long.parseLong(m.group(1)));
|
---|
| 114 | for (String s : m.group(2).split(",")) {
|
---|
[4821] | 115 | refs.add(new Relation(Long.parseLong(s)));
|
---|
[4816] | 116 | }
|
---|
| 117 | return Pair.create(n, refs);
|
---|
| 118 | }
|
---|
[8509] | 119 | m = Pattern.compile(".*Way (\\d+) requires the nodes with id in " + ids + ".*").matcher(msg);
|
---|
| 120 | // ... ", which either do not exist, or are not visible"
|
---|
[4816] | 121 | if (m.matches()) {
|
---|
| 122 | OsmPrimitive n = new Way(Long.parseLong(m.group(1)));
|
---|
| 123 | for (String s : m.group(2).split(",")) {
|
---|
[4821] | 124 | refs.add(new Node(Long.parseLong(s)));
|
---|
[4816] | 125 | }
|
---|
| 126 | return Pair.create(n, refs);
|
---|
| 127 | }
|
---|
| 128 | return null;
|
---|
[2246] | 129 | }
|
---|
[2097] | 130 |
|
---|
| 131 | /**
|
---|
| 132 | * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412
|
---|
| 133 | *
|
---|
| 134 | * @param e the exception
|
---|
[7205] | 135 | * @return The HTML formatted error message to display
|
---|
[2097] | 136 | */
|
---|
| 137 | public static String explainPreconditionFailed(OsmApiException e) {
|
---|
[6643] | 138 | Main.error(e);
|
---|
[4816] | 139 | Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict = parsePreconditionFailed(e.getErrorHeader());
|
---|
| 140 | if (conflict != null) {
|
---|
| 141 | OsmPrimitive firstRefs = conflict.b.iterator().next();
|
---|
[5321] | 142 | String objId = Long.toString(conflict.a.getId());
|
---|
[10717] | 143 | Collection<Long> refIds = Utils.transform(conflict.b, OsmPrimitive::getId);
|
---|
[4816] | 144 | String refIdsString = refIds.size() == 1 ? refIds.iterator().next().toString() : refIds.toString();
|
---|
| 145 | if (conflict.a instanceof Node) {
|
---|
| 146 | if (firstRefs instanceof Node) {
|
---|
| 147 | return "<html>" + trn(
|
---|
| 148 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 149 | + " It is still referred to by node {1}.<br>"
|
---|
| 150 | + "Please load the node, remove the reference to the node, and upload again.",
|
---|
| 151 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 152 | + " It is still referred to by nodes {1}.<br>"
|
---|
| 153 | + "Please load the nodes, remove the reference to the node, and upload again.",
|
---|
| 154 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 155 | } else if (firstRefs instanceof Way) {
|
---|
| 156 | return "<html>" + trn(
|
---|
| 157 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 158 | + " It is still referred to by way {1}.<br>"
|
---|
| 159 | + "Please load the way, remove the reference to the node, and upload again.",
|
---|
| 160 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 161 | + " It is still referred to by ways {1}.<br>"
|
---|
| 162 | + "Please load the ways, remove the reference to the node, and upload again.",
|
---|
| 163 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 164 | } else if (firstRefs instanceof Relation) {
|
---|
| 165 | return "<html>" + trn(
|
---|
| 166 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 167 | + " It is still referred to by relation {1}.<br>"
|
---|
| 168 | + "Please load the relation, remove the reference to the node, and upload again.",
|
---|
| 169 | "<strong>Failed</strong> to delete <strong>node {0}</strong>."
|
---|
| 170 | + " It is still referred to by relations {1}.<br>"
|
---|
| 171 | + "Please load the relations, remove the reference to the node, and upload again.",
|
---|
| 172 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 173 | } else {
|
---|
| 174 | throw new IllegalStateException();
|
---|
| 175 | }
|
---|
| 176 | } else if (conflict.a instanceof Way) {
|
---|
| 177 | if (firstRefs instanceof Node) {
|
---|
| 178 | return "<html>" + trn(
|
---|
| 179 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 180 | + " It is still referred to by node {1}.<br>"
|
---|
| 181 | + "Please load the node, remove the reference to the way, and upload again.",
|
---|
| 182 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 183 | + " It is still referred to by nodes {1}.<br>"
|
---|
| 184 | + "Please load the nodes, remove the reference to the way, and upload again.",
|
---|
| 185 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 186 | } else if (firstRefs instanceof Way) {
|
---|
| 187 | return "<html>" + trn(
|
---|
| 188 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 189 | + " It is still referred to by way {1}.<br>"
|
---|
| 190 | + "Please load the way, remove the reference to the way, and upload again.",
|
---|
| 191 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 192 | + " It is still referred to by ways {1}.<br>"
|
---|
| 193 | + "Please load the ways, remove the reference to the way, and upload again.",
|
---|
| 194 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 195 | } else if (firstRefs instanceof Relation) {
|
---|
| 196 | return "<html>" + trn(
|
---|
| 197 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 198 | + " It is still referred to by relation {1}.<br>"
|
---|
| 199 | + "Please load the relation, remove the reference to the way, and upload again.",
|
---|
| 200 | "<strong>Failed</strong> to delete <strong>way {0}</strong>."
|
---|
| 201 | + " It is still referred to by relations {1}.<br>"
|
---|
| 202 | + "Please load the relations, remove the reference to the way, and upload again.",
|
---|
| 203 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 204 | } else {
|
---|
| 205 | throw new IllegalStateException();
|
---|
| 206 | }
|
---|
| 207 | } else if (conflict.a instanceof Relation) {
|
---|
| 208 | if (firstRefs instanceof Node) {
|
---|
| 209 | return "<html>" + trn(
|
---|
| 210 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 211 | + " It is still referred to by node {1}.<br>"
|
---|
| 212 | + "Please load the node, remove the reference to the relation, and upload again.",
|
---|
| 213 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 214 | + " It is still referred to by nodes {1}.<br>"
|
---|
| 215 | + "Please load the nodes, remove the reference to the relation, and upload again.",
|
---|
| 216 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 217 | } else if (firstRefs instanceof Way) {
|
---|
| 218 | return "<html>" + trn(
|
---|
| 219 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 220 | + " It is still referred to by way {1}.<br>"
|
---|
| 221 | + "Please load the way, remove the reference to the relation, and upload again.",
|
---|
| 222 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 223 | + " It is still referred to by ways {1}.<br>"
|
---|
| 224 | + "Please load the ways, remove the reference to the relation, and upload again.",
|
---|
| 225 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 226 | } else if (firstRefs instanceof Relation) {
|
---|
| 227 | return "<html>" + trn(
|
---|
| 228 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 229 | + " It is still referred to by relation {1}.<br>"
|
---|
| 230 | + "Please load the relation, remove the reference to the relation, and upload again.",
|
---|
| 231 | "<strong>Failed</strong> to delete <strong>relation {0}</strong>."
|
---|
| 232 | + " It is still referred to by relations {1}.<br>"
|
---|
| 233 | + "Please load the relations, remove the reference to the relation, and upload again.",
|
---|
| 234 | conflict.b.size(), objId, refIdsString) + "</html>";
|
---|
| 235 | } else {
|
---|
| 236 | throw new IllegalStateException();
|
---|
| 237 | }
|
---|
| 238 | } else {
|
---|
| 239 | throw new IllegalStateException();
|
---|
[4691] | 240 | }
|
---|
[4816] | 241 | } else {
|
---|
| 242 | return tr(
|
---|
| 243 | "<html>Uploading to the server <strong>failed</strong> because your current<br>"
|
---|
| 244 | + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>",
|
---|
[8756] | 245 | Utils.escapeReservedCharactersHTML(e.getMessage()));
|
---|
[2246] | 246 | }
|
---|
[2097] | 247 | }
|
---|
| 248 |
|
---|
[9474] | 249 | /**
|
---|
| 250 | * Explains a {@link OsmApiException} which was thrown because the authentication at
|
---|
| 251 | * the OSM server failed, with basic authentication.
|
---|
| 252 | *
|
---|
| 253 | * @param e the exception
|
---|
| 254 | * @return The HTML formatted error message to display
|
---|
| 255 | */
|
---|
[2748] | 256 | public static String explainFailedBasicAuthentication(OsmApiException e) {
|
---|
[6643] | 257 | Main.error(e);
|
---|
[2748] | 258 | return tr("<html>"
|
---|
| 259 | + "Authentication at the OSM server with the username ''{0}'' failed.<br>"
|
---|
| 260 | + "Please check the username and the password in the JOSM preferences."
|
---|
| 261 | + "</html>",
|
---|
[4263] | 262 | CredentialsManager.getInstance().getUsername()
|
---|
[2748] | 263 | );
|
---|
| 264 | }
|
---|
| 265 |
|
---|
[9474] | 266 | /**
|
---|
| 267 | * Explains a {@link OsmApiException} which was thrown because the authentication at
|
---|
| 268 | * the OSM server failed, with OAuth authentication.
|
---|
| 269 | *
|
---|
| 270 | * @param e the exception
|
---|
| 271 | * @return The HTML formatted error message to display
|
---|
| 272 | */
|
---|
[2748] | 273 | public static String explainFailedOAuthAuthentication(OsmApiException e) {
|
---|
[6643] | 274 | Main.error(e);
|
---|
[2748] | 275 | return tr("<html>"
|
---|
| 276 | + "Authentication at the OSM server with the OAuth token ''{0}'' failed.<br>"
|
---|
| 277 | + "Please launch the preferences dialog and retrieve another OAuth token."
|
---|
| 278 | + "</html>",
|
---|
| 279 | OAuthAccessTokenHolder.getInstance().getAccessTokenKey()
|
---|
| 280 | );
|
---|
| 281 | }
|
---|
| 282 |
|
---|
[9474] | 283 | /**
|
---|
| 284 | * Explains a {@link OsmApiException} which was thrown because accessing a protected
|
---|
| 285 | * resource was forbidden (HTTP 403), without OAuth authentication.
|
---|
| 286 | *
|
---|
| 287 | * @param e the exception
|
---|
| 288 | * @return The HTML formatted error message to display
|
---|
| 289 | */
|
---|
[4020] | 290 | public static String explainFailedAuthorisation(OsmApiException e) {
|
---|
[6643] | 291 | Main.error(e);
|
---|
[4020] | 292 | String header = e.getErrorHeader();
|
---|
| 293 | String body = e.getErrorBody();
|
---|
[9474] | 294 | String msg;
|
---|
[4020] | 295 | if (header != null) {
|
---|
| 296 | if (body != null && !header.equals(body)) {
|
---|
[8846] | 297 | msg = header + " (" + body + ')';
|
---|
[4020] | 298 | } else {
|
---|
| 299 | msg = header;
|
---|
| 300 | }
|
---|
| 301 | } else {
|
---|
| 302 | msg = body;
|
---|
| 303 | }
|
---|
[6070] | 304 |
|
---|
[5584] | 305 | if (msg != null && !msg.isEmpty()) {
|
---|
| 306 | return tr("<html>"
|
---|
| 307 | + "Authorisation at the OSM server failed.<br>"
|
---|
| 308 | + "The server reported the following error:<br>"
|
---|
| 309 | + "''{0}''"
|
---|
| 310 | + "</html>",
|
---|
| 311 | msg
|
---|
| 312 | );
|
---|
| 313 | } else {
|
---|
| 314 | return tr("<html>"
|
---|
| 315 | + "Authorisation at the OSM server failed.<br>"
|
---|
| 316 | + "</html>"
|
---|
| 317 | );
|
---|
| 318 | }
|
---|
[4020] | 319 | }
|
---|
| 320 |
|
---|
[9474] | 321 | /**
|
---|
| 322 | * Explains a {@link OsmApiException} which was thrown because accessing a protected
|
---|
| 323 | * resource was forbidden (HTTP 403), with OAuth authentication.
|
---|
| 324 | *
|
---|
| 325 | * @param e the exception
|
---|
| 326 | * @return The HTML formatted error message to display
|
---|
| 327 | */
|
---|
[2748] | 328 | public static String explainFailedOAuthAuthorisation(OsmApiException e) {
|
---|
[6643] | 329 | Main.error(e);
|
---|
[2748] | 330 | return tr("<html>"
|
---|
| 331 | + "Authorisation at the OSM server with the OAuth token ''{0}'' failed.<br>"
|
---|
| 332 | + "The token is not authorised to access the protected resource<br>"
|
---|
| 333 | + "''{1}''.<br>"
|
---|
| 334 | + "Please launch the preferences dialog and retrieve another OAuth token."
|
---|
| 335 | + "</html>",
|
---|
| 336 | OAuthAccessTokenHolder.getInstance().getAccessTokenKey(),
|
---|
| 337 | e.getAccessedUrl() == null ? tr("unknown") : e.getAccessedUrl()
|
---|
| 338 | );
|
---|
| 339 | }
|
---|
[3101] | 340 |
|
---|
[2097] | 341 | /**
|
---|
[3101] | 342 | * Explains an OSM API exception because of a client timeout (HTTP 408).
|
---|
[3255] | 343 | *
|
---|
[3101] | 344 | * @param e the exception
|
---|
[7205] | 345 | * @return The HTML formatted error message to display
|
---|
[3101] | 346 | */
|
---|
| 347 | public static String explainClientTimeout(OsmApiException e) {
|
---|
[6643] | 348 | Main.error(e);
|
---|
[3101] | 349 | return tr("<html>"
|
---|
| 350 | + "Communication with the OSM server ''{0}'' timed out. Please retry later."
|
---|
| 351 | + "</html>",
|
---|
[9876] | 352 | getUrlFromException(e)
|
---|
[3101] | 353 | );
|
---|
| 354 | }
|
---|
| 355 |
|
---|
| 356 | /**
|
---|
| 357 | * Replies a generic error message for an OSM API exception
|
---|
[3255] | 358 | *
|
---|
[3101] | 359 | * @param e the exception
|
---|
[7205] | 360 | * @return The HTML formatted error message to display
|
---|
[3101] | 361 | */
|
---|
| 362 | public static String explainGenericOsmApiException(OsmApiException e) {
|
---|
[6643] | 363 | Main.error(e);
|
---|
[3101] | 364 | String errMsg = e.getErrorHeader();
|
---|
| 365 | if (errMsg == null) {
|
---|
| 366 | errMsg = e.getErrorBody();
|
---|
| 367 | }
|
---|
| 368 | if (errMsg == null) {
|
---|
| 369 | errMsg = tr("no error message available");
|
---|
| 370 | }
|
---|
| 371 | return tr("<html>"
|
---|
| 372 | + "Communication with the OSM server ''{0}''failed. The server replied<br>"
|
---|
| 373 | + "the following error code and the following error message:<br>"
|
---|
| 374 | + "<strong>Error code:<strong> {1}<br>"
|
---|
| 375 | + "<strong>Error message (untranslated)</strong>: {2}"
|
---|
| 376 | + "</html>",
|
---|
[9876] | 377 | getUrlFromException(e),
|
---|
[3101] | 378 | e.getResponseCode(),
|
---|
| 379 | errMsg
|
---|
| 380 | );
|
---|
| 381 | }
|
---|
| 382 |
|
---|
| 383 | /**
|
---|
[2289] | 384 | * Explains an error due to a 409 conflict
|
---|
| 385 | *
|
---|
| 386 | * @param e the exception
|
---|
[7205] | 387 | * @return The HTML formatted error message to display
|
---|
[2289] | 388 | */
|
---|
| 389 | public static String explainConflict(OsmApiException e) {
|
---|
[6643] | 390 | Main.error(e);
|
---|
[2289] | 391 | String msg = e.getErrorHeader();
|
---|
| 392 | if (msg != null) {
|
---|
[9474] | 393 | Matcher m = Pattern.compile("The changeset (\\d+) was closed at (.*)").matcher(msg);
|
---|
[2289] | 394 | if (m.matches()) {
|
---|
| 395 | long changesetId = Long.parseLong(m.group(1));
|
---|
| 396 | Date closeDate = null;
|
---|
| 397 | try {
|
---|
[7299] | 398 | closeDate = DateUtils.newOsmApiDateTimeFormat().parse(m.group(2));
|
---|
[6248] | 399 | } catch (ParseException ex) {
|
---|
| 400 | Main.error(tr("Failed to parse date ''{0}'' replied by server.", m.group(2)));
|
---|
[6643] | 401 | Main.error(ex);
|
---|
[2289] | 402 | }
|
---|
| 403 | if (closeDate == null) {
|
---|
| 404 | msg = tr(
|
---|
[4816] | 405 | "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.",
|
---|
[2289] | 406 | changesetId
|
---|
| 407 | );
|
---|
| 408 | } else {
|
---|
| 409 | msg = tr(
|
---|
| 410 | "<html>Closing of changeset <strong>{0}</strong> failed<br>"
|
---|
[4816] | 411 | +" because it has already been closed on {1}.",
|
---|
[2289] | 412 | changesetId,
|
---|
[7299] | 413 | DateUtils.formatDateTime(closeDate, DateFormat.DEFAULT, DateFormat.DEFAULT)
|
---|
[2289] | 414 | );
|
---|
| 415 | }
|
---|
| 416 | return msg;
|
---|
| 417 | }
|
---|
| 418 | msg = tr(
|
---|
| 419 | "<html>The server reported that it has detected a conflict.<br>" +
|
---|
[2387] | 420 | "Error message (untranslated):<br>{0}</html>",
|
---|
[2289] | 421 | msg
|
---|
| 422 | );
|
---|
[4816] | 423 | } else {
|
---|
| 424 | msg = tr(
|
---|
| 425 | "<html>The server reported that it has detected a conflict.");
|
---|
[2289] | 426 | }
|
---|
[9474] | 427 | return msg.endsWith("</html>") ? msg : (msg + "</html>");
|
---|
[2289] | 428 | }
|
---|
| 429 |
|
---|
| 430 | /**
|
---|
[2480] | 431 | * Explains an exception thrown during upload because the changeset which data is
|
---|
| 432 | * uploaded to is already closed.
|
---|
[2512] | 433 | *
|
---|
[2480] | 434 | * @param e the exception
|
---|
[7205] | 435 | * @return The HTML formatted error message to display
|
---|
[2480] | 436 | */
|
---|
| 437 | public static String explainChangesetClosedException(ChangesetClosedException e) {
|
---|
[7024] | 438 | Main.error(e);
|
---|
| 439 | return tr(
|
---|
[2480] | 440 | "<html>Failed to upload to changeset <strong>{0}</strong><br>"
|
---|
[4816] | 441 | +"because it has already been closed on {1}.",
|
---|
[2480] | 442 | e.getChangesetId(),
|
---|
[7299] | 443 | e.getClosedOn() == null ? "?" : DateUtils.formatDateTime(e.getClosedOn(), DateFormat.DEFAULT, DateFormat.DEFAULT)
|
---|
[2480] | 444 | );
|
---|
| 445 | }
|
---|
| 446 |
|
---|
| 447 | /**
|
---|
[2097] | 448 | * Explains an exception with a generic message dialog
|
---|
[2512] | 449 | *
|
---|
[2097] | 450 | * @param e the exception
|
---|
[7205] | 451 | * @return The HTML formatted error message to display
|
---|
[2097] | 452 | */
|
---|
| 453 | public static String explainGeneric(Exception e) {
|
---|
| 454 | String msg = e.getMessage();
|
---|
[6087] | 455 | if (msg == null || msg.trim().isEmpty()) {
|
---|
[2097] | 456 | msg = e.toString();
|
---|
| 457 | }
|
---|
[6643] | 458 | Main.error(e);
|
---|
[8756] | 459 | return Utils.escapeReservedCharactersHTML(msg);
|
---|
[2097] | 460 | }
|
---|
| 461 |
|
---|
| 462 | /**
|
---|
[5266] | 463 | * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}.
|
---|
[2097] | 464 | * This is most likely happening when user tries to access the OSM API from within an
|
---|
| 465 | * applet which wasn't loaded from the API server.
|
---|
[2512] | 466 | *
|
---|
[2097] | 467 | * @param e the exception
|
---|
[7205] | 468 | * @return The HTML formatted error message to display
|
---|
[2097] | 469 | */
|
---|
| 470 | public static String explainSecurityException(OsmTransferException e) {
|
---|
[3255] | 471 | String apiUrl = e.getUrl();
|
---|
[2097] | 472 | String host = tr("unknown");
|
---|
| 473 | try {
|
---|
| 474 | host = new URL(apiUrl).getHost();
|
---|
| 475 | } catch (MalformedURLException ex) {
|
---|
| 476 | // shouldn't happen
|
---|
[10626] | 477 | Main.trace(ex);
|
---|
[2097] | 478 | }
|
---|
| 479 |
|
---|
[7024] | 480 | return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>"
|
---|
[2097] | 481 | + "for security reasons. This is most likely because you are running<br>"
|
---|
[9474] | 482 | + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host)+"</html>";
|
---|
[2097] | 483 | }
|
---|
| 484 |
|
---|
| 485 | /**
|
---|
[5266] | 486 | * Explains a {@link SocketException} which has caused an {@link OsmTransferException}.
|
---|
[2097] | 487 | * This is most likely because there's not connection to the Internet or because
|
---|
| 488 | * the remote server is not reachable.
|
---|
[2512] | 489 | *
|
---|
[2097] | 490 | * @param e the exception
|
---|
[7205] | 491 | * @return The HTML formatted error message to display
|
---|
[2097] | 492 | */
|
---|
| 493 | public static String explainNestedSocketException(OsmTransferException e) {
|
---|
[6643] | 494 | Main.error(e);
|
---|
[7024] | 495 | return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
|
---|
[9474] | 496 | + "Please check your internet connection.", e.getUrl())+"</html>";
|
---|
[2097] | 497 | }
|
---|
| 498 |
|
---|
| 499 | /**
|
---|
[5266] | 500 | * Explains a {@link IOException} which has caused an {@link OsmTransferException}.
|
---|
[2097] | 501 | * This is most likely happening when the communication with the remote server is
|
---|
| 502 | * interrupted for any reason.
|
---|
[2512] | 503 | *
|
---|
[2097] | 504 | * @param e the exception
|
---|
[7205] | 505 | * @return The HTML formatted error message to display
|
---|
[2097] | 506 | */
|
---|
| 507 | public static String explainNestedIOException(OsmTransferException e) {
|
---|
| 508 | IOException ioe = getNestedException(e, IOException.class);
|
---|
[7024] | 509 | Main.error(e);
|
---|
| 510 | return tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>"
|
---|
[5525] | 511 | + "due to a problem with transferring data.<br>"
|
---|
[9474] | 512 | + "Details (untranslated): {1}</html>", e.getUrl(),
|
---|
| 513 | ioe != null ? ioe.getMessage() : "null");
|
---|
[2097] | 514 | }
|
---|
| 515 |
|
---|
| 516 | /**
|
---|
[5266] | 517 | * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}.
|
---|
[7187] | 518 | * This is most likely happening when JOSM tries to load data in an unsupported format.
|
---|
[3069] | 519 | *
|
---|
| 520 | * @param e the exception
|
---|
[7205] | 521 | * @return The HTML formatted error message to display
|
---|
[3069] | 522 | */
|
---|
| 523 | public static String explainNestedIllegalDataException(OsmTransferException e) {
|
---|
| 524 | IllegalDataException ide = getNestedException(e, IllegalDataException.class);
|
---|
[7024] | 525 | Main.error(e);
|
---|
| 526 | return tr("<html>Failed to download data. "
|
---|
[3112] | 527 | + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>"
|
---|
[9474] | 528 | + "<br>Details (untranslated): {0}</html>", ide != null ? ide.getMessage() : "null");
|
---|
[3069] | 529 | }
|
---|
| 530 |
|
---|
| 531 | /**
|
---|
[7434] | 532 | * Explains a {@link OfflineAccessException} which has caused an {@link OsmTransferException}.
|
---|
| 533 | * This is most likely happening when JOSM tries to access OSM API or JOSM website while in offline mode.
|
---|
| 534 | *
|
---|
| 535 | * @param e the exception
|
---|
| 536 | * @return The HTML formatted error message to display
|
---|
| 537 | * @since 7434
|
---|
| 538 | */
|
---|
| 539 | public static String explainOfflineAccessException(OsmTransferException e) {
|
---|
| 540 | OfflineAccessException oae = getNestedException(e, OfflineAccessException.class);
|
---|
| 541 | Main.error(e);
|
---|
| 542 | return tr("<html>Failed to download data.<br>"
|
---|
[9474] | 543 | + "<br>Details: {0}</html>", oae != null ? oae.getMessage() : "null");
|
---|
[7434] | 544 | }
|
---|
| 545 |
|
---|
| 546 | /**
|
---|
[5266] | 547 | * Explains a {@link OsmApiException} which was thrown because of an internal server
|
---|
[9474] | 548 | * error in the OSM API server.
|
---|
[2512] | 549 | *
|
---|
[2097] | 550 | * @param e the exception
|
---|
[7205] | 551 | * @return The HTML formatted error message to display
|
---|
[2097] | 552 | */
|
---|
| 553 | public static String explainInternalServerError(OsmTransferException e) {
|
---|
[6643] | 554 | Main.error(e);
|
---|
[7024] | 555 | return tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>"
|
---|
[9474] | 556 | + "This is most likely a temporary problem. Please try again later.", e.getUrl())+"</html>";
|
---|
[2097] | 557 | }
|
---|
| 558 |
|
---|
| 559 | /**
|
---|
[7205] | 560 | * Explains a {@link OsmApiException} which was thrown because of a bad request.
|
---|
[2512] | 561 | *
|
---|
[2097] | 562 | * @param e the exception
|
---|
[7205] | 563 | * @return The HTML formatted error message to display
|
---|
[2097] | 564 | */
|
---|
| 565 | public static String explainBadRequest(OsmApiException e) {
|
---|
[9876] | 566 | String message = tr("The OSM server ''{0}'' reported a bad request.<br>", getUrlFromException(e));
|
---|
[7205] | 567 | String errorHeader = e.getErrorHeader();
|
---|
| 568 | if (errorHeader != null && (errorHeader.startsWith("The maximum bbox") ||
|
---|
| 569 | errorHeader.startsWith("You requested too many nodes"))) {
|
---|
[2097] | 570 | message += "<br>"
|
---|
| 571 | + tr("The area you tried to download is too big or your request was too large."
|
---|
| 572 | + "<br>Either request a smaller area or use an export file provided by the OSM community.");
|
---|
[7205] | 573 | } else if (errorHeader != null) {
|
---|
| 574 | message += tr("<br>Error message(untranslated): {0}", errorHeader);
|
---|
[2097] | 575 | }
|
---|
[6643] | 576 | Main.error(e);
|
---|
[7024] | 577 | return "<html>" + message + "</html>";
|
---|
[2097] | 578 | }
|
---|
[6070] | 579 |
|
---|
[4482] | 580 | /**
|
---|
[5266] | 581 | * Explains a {@link OsmApiException} which was thrown because of
|
---|
[6070] | 582 | * bandwidth limit exceeded (HTTP error 509)
|
---|
[4482] | 583 | *
|
---|
| 584 | * @param e the exception
|
---|
[7205] | 585 | * @return The HTML formatted error message to display
|
---|
[4482] | 586 | */
|
---|
| 587 | public static String explainBandwidthLimitExceeded(OsmApiException e) {
|
---|
[7024] | 588 | Main.error(e);
|
---|
[4482] | 589 | // TODO: Write a proper error message
|
---|
[7024] | 590 | return explainGenericOsmApiException(e);
|
---|
[4482] | 591 | }
|
---|
[2097] | 592 |
|
---|
| 593 | /**
|
---|
[5266] | 594 | * Explains a {@link OsmApiException} which was thrown because a resource wasn't found.
|
---|
[2512] | 595 | *
|
---|
[2189] | 596 | * @param e the exception
|
---|
[7205] | 597 | * @return The HTML formatted error message to display
|
---|
[2189] | 598 | */
|
---|
| 599 | public static String explainNotFound(OsmApiException e) {
|
---|
[2853] | 600 | String message = tr("The OSM server ''{0}'' does not know about an object<br>"
|
---|
[2200] | 601 | + "you tried to read, update, or delete. Either the respective object<br>"
|
---|
[2853] | 602 | + "does not exist on the server or you are using an invalid URL to access<br>"
|
---|
[8836] | 603 | + "it. Please carefully check the server''s address ''{0}'' for typos.",
|
---|
[9876] | 604 | getUrlFromException(e));
|
---|
[6643] | 605 | Main.error(e);
|
---|
[7024] | 606 | return "<html>" + message + "</html>";
|
---|
[2189] | 607 | }
|
---|
| 608 |
|
---|
| 609 | /**
|
---|
[5266] | 610 | * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}.
|
---|
[2097] | 611 | * This is most likely happening when there is an error in the API URL or when
|
---|
| 612 | * local DNS services are not working.
|
---|
[2512] | 613 | *
|
---|
[2097] | 614 | * @param e the exception
|
---|
[7205] | 615 | * @return The HTML formatted error message to display
|
---|
[2097] | 616 | */
|
---|
[3255] | 617 | public static String explainNestedUnknownHostException(OsmTransferException e) {
|
---|
| 618 | String apiUrl = e.getUrl();
|
---|
[2097] | 619 | String host = tr("unknown");
|
---|
| 620 | try {
|
---|
| 621 | host = new URL(apiUrl).getHost();
|
---|
| 622 | } catch (MalformedURLException ex) {
|
---|
| 623 | // shouldn't happen
|
---|
[10626] | 624 | Main.trace(e);
|
---|
[2097] | 625 | }
|
---|
| 626 |
|
---|
[7024] | 627 | Main.error(e);
|
---|
| 628 | return tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
|
---|
[2853] | 629 | + "Host name ''{1}'' could not be resolved. <br>"
|
---|
[9474] | 630 | + "Please check the API URL in your preferences and your internet connection.", apiUrl, host)+"</html>";
|
---|
[2097] | 631 | }
|
---|
| 632 |
|
---|
| 633 | /**
|
---|
| 634 | * Replies the first nested exception of type <code>nestedClass</code> (including
|
---|
| 635 | * the root exception <code>e</code>) or null, if no such exception is found.
|
---|
[2512] | 636 | *
|
---|
[8470] | 637 | * @param <T> nested exception type
|
---|
[2097] | 638 | * @param e the root exception
|
---|
| 639 | * @param nestedClass the type of the nested exception
|
---|
| 640 | * @return the first nested exception of type <code>nestedClass</code> (including
|
---|
| 641 | * the root exception <code>e</code>) or null, if no such exception is found.
|
---|
[8470] | 642 | * @since 8470
|
---|
[2097] | 643 | */
|
---|
[8470] | 644 | public static <T> T getNestedException(Exception e, Class<T> nestedClass) {
|
---|
[2097] | 645 | Throwable t = e;
|
---|
| 646 | while (t != null && !(nestedClass.isInstance(t))) {
|
---|
| 647 | t = t.getCause();
|
---|
| 648 | }
|
---|
| 649 | if (t == null)
|
---|
| 650 | return null;
|
---|
| 651 | else if (nestedClass.isInstance(t))
|
---|
| 652 | return nestedClass.cast(t);
|
---|
| 653 | return null;
|
---|
| 654 | }
|
---|
| 655 |
|
---|
| 656 | /**
|
---|
[5266] | 657 | * Explains an {@link OsmTransferException} to the user.
|
---|
[2512] | 658 | *
|
---|
[5266] | 659 | * @param e the {@link OsmTransferException}
|
---|
[7205] | 660 | * @return The HTML formatted error message to display
|
---|
[2097] | 661 | */
|
---|
| 662 | public static String explainOsmTransferException(OsmTransferException e) {
|
---|
| 663 | if (getNestedException(e, SecurityException.class) != null)
|
---|
| 664 | return explainSecurityException(e);
|
---|
| 665 | if (getNestedException(e, SocketException.class) != null)
|
---|
| 666 | return explainNestedSocketException(e);
|
---|
| 667 | if (getNestedException(e, UnknownHostException.class) != null)
|
---|
[3255] | 668 | return explainNestedUnknownHostException(e);
|
---|
[2097] | 669 | if (getNestedException(e, IOException.class) != null)
|
---|
| 670 | return explainNestedIOException(e);
|
---|
| 671 | if (e instanceof OsmApiInitializationException)
|
---|
| 672 | return explainOsmApiInitializationException((OsmApiInitializationException) e);
|
---|
| 673 |
|
---|
[2480] | 674 | if (e instanceof ChangesetClosedException)
|
---|
[8510] | 675 | return explainChangesetClosedException((ChangesetClosedException) e);
|
---|
[2480] | 676 |
|
---|
[2097] | 677 | if (e instanceof OsmApiException) {
|
---|
| 678 | OsmApiException oae = (OsmApiException) e;
|
---|
| 679 | if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED)
|
---|
| 680 | return explainPreconditionFailed(oae);
|
---|
| 681 | if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE)
|
---|
| 682 | return explainGoneForUnknownPrimitive(oae);
|
---|
| 683 | if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR)
|
---|
| 684 | return explainInternalServerError(oae);
|
---|
| 685 | if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST)
|
---|
| 686 | return explainBadRequest(oae);
|
---|
[4482] | 687 | if (oae.getResponseCode() == 509)
|
---|
| 688 | return explainBandwidthLimitExceeded(oae);
|
---|
[2097] | 689 | }
|
---|
| 690 | return explainGeneric(e);
|
---|
| 691 | }
|
---|
| 692 |
|
---|
| 693 | /**
|
---|
| 694 | * explains the case of an error due to a delete request on an already deleted
|
---|
[5266] | 695 | * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
|
---|
| 696 | * {@link OsmPrimitive} is causing the error.
|
---|
[2097] | 697 | *
|
---|
| 698 | * @param e the exception
|
---|
[7205] | 699 | * @return The HTML formatted error message to display
|
---|
[2097] | 700 | */
|
---|
| 701 | public static String explainGoneForUnknownPrimitive(OsmApiException e) {
|
---|
[7024] | 702 | return tr(
|
---|
[2240] | 703 | "<html>The server reports that an object is deleted.<br>"
|
---|
| 704 | + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> "
|
---|
| 705 | + "<strong>Downloading failed</strong> if you tried to download this object.<br>"
|
---|
| 706 | + "<br>"
|
---|
| 707 | + "The error message is:<br>" + "{0}"
|
---|
[8756] | 708 | + "</html>", Utils.escapeReservedCharactersHTML(e.getMessage()));
|
---|
[2097] | 709 | }
|
---|
| 710 |
|
---|
| 711 | /**
|
---|
[5266] | 712 | * Explains an {@link Exception} to the user.
|
---|
[2512] | 713 | *
|
---|
[5266] | 714 | * @param e the {@link Exception}
|
---|
[7205] | 715 | * @return The HTML formatted error message to display
|
---|
[2097] | 716 | */
|
---|
| 717 | public static String explainException(Exception e) {
|
---|
[7024] | 718 | Main.error(e);
|
---|
[2240] | 719 | if (e instanceof OsmTransferException) {
|
---|
[7024] | 720 | return explainOsmTransferException((OsmTransferException) e);
|
---|
[2240] | 721 | } else {
|
---|
[7024] | 722 | return explainGeneric(e);
|
---|
[2240] | 723 | }
|
---|
[2097] | 724 | }
|
---|
[9876] | 725 |
|
---|
| 726 | static String getUrlFromException(OsmApiException e) {
|
---|
| 727 | if (e.getAccessedUrl() != null) {
|
---|
| 728 | try {
|
---|
| 729 | return new URL(e.getAccessedUrl()).getHost();
|
---|
| 730 | } catch (MalformedURLException e1) {
|
---|
| 731 | Main.warn(e1);
|
---|
| 732 | }
|
---|
| 733 | }
|
---|
| 734 | if (e.getUrl() != null) {
|
---|
| 735 | return e.getUrl();
|
---|
| 736 | } else {
|
---|
| 737 | return OsmApi.getOsmApi().getBaseUrl();
|
---|
| 738 | }
|
---|
| 739 | }
|
---|
[2097] | 740 | }
|
---|