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