source: josm/trunk/src/org/openstreetmap/josm/tools/ExceptionUtil.java@ 12686

Last change on this file since 12686 was 12686, checked in by Don-vip, 7 years ago

see #15182 - move OAuthAccessTokenHolder from gui.preferences.server to data.oauth

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