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

Last change on this file since 4175 was 4036, checked in by bastiK, 13 years ago

detect and translate new api error

  • Property svn:eol-style set to native
File size: 21.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.net.HttpURLConnection;
8import java.net.MalformedURLException;
9import java.net.SocketException;
10import java.net.URL;
11import java.net.UnknownHostException;
12import java.text.DateFormat;
13import java.text.ParseException;
14import java.text.SimpleDateFormat;
15import java.util.Date;
16import java.util.Locale;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
22import org.openstreetmap.josm.io.ChangesetClosedException;
23import org.openstreetmap.josm.io.IllegalDataException;
24import org.openstreetmap.josm.io.MissingOAuthAccessTokenException;
25import org.openstreetmap.josm.io.OsmApi;
26import org.openstreetmap.josm.io.OsmApiException;
27import org.openstreetmap.josm.io.OsmApiInitializationException;
28import org.openstreetmap.josm.io.OsmTransferException;
29
30public class ExceptionUtil {
31 private ExceptionUtil() {
32 }
33
34 /**
35 * handles an exception caught during OSM API initialization
36 *
37 * @param e the exception
38 */
39 public static String explainOsmApiInitializationException(OsmApiInitializationException e) {
40 e.printStackTrace();
41 String msg = tr(
42 "<html>Failed to initialize communication with the OSM server {0}.<br>"
43 + "Check the server URL in your preferences and your internet connection.</html>", Main.pref.get(
44 "osm-server.url", "http://api.openstreetmap.org/api"));
45 return msg;
46 }
47
48
49 /**
50 * Creates the error message
51 *
52 * @param e the exception
53 */
54 public static String explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) {
55 e.printStackTrace();
56 String msg = tr(
57 "<html>Failed to authenticate at the OSM server ''{0}''.<br>"
58 + "You are using OAuth to authenticate but currently there is no<br>"
59 + "OAuth Access Token configured.<br>"
60 + "Please open the Preferences Dialog and generate or enter an Access Token."
61 + "</html>",
62 Main.pref.get("osm-server.url", "http://api.openstreetmap.org/api")
63 );
64 return msg;
65 }
66
67 /**
68 * Explains a precondition exception when a child relation could not be deleted because
69 * it is still referred to by an undeleted parent relation.
70 *
71 * @param e the exception
72 * @param childRelation the child relation
73 * @param parentRelation the parent relation
74 * @return
75 */
76 public static String explainDeletedRelationStillInUse(OsmApiException e, long childRelation, long parentRelation) {
77 String msg = tr(
78 "<html><strong>Failed</strong> to delete <strong>relation {0}</strong>."
79 + " It is still referred to by relation {1}.<br>"
80 + "Please load relation {1}, remove the reference to relation {0}, and upload again.</html>",
81 childRelation,parentRelation
82 );
83 return msg;
84 }
85
86 /**
87 * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412
88 *
89 * @param e the exception
90 */
91 public static String explainPreconditionFailed(OsmApiException e) {
92 e.printStackTrace();
93 String msg = e.getErrorHeader();
94 if (msg != null) {
95 String pattern = "Precondition failed: The relation (\\d+) is used in relation (\\d+)\\.";
96 Pattern p = Pattern.compile(pattern);
97 Matcher m = p.matcher(msg);
98 if (m.matches()) {
99 long childRelation = Long.parseLong(m.group(1));
100 long parentRelation = Long.parseLong(m.group(2));
101 return explainDeletedRelationStillInUse(e, childRelation, parentRelation);
102 }
103 }
104 msg = tr(
105 "<html>Uploading to the server <strong>failed</strong> because your current<br>"
106 + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>", e
107 .getMessage().replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"));
108 return msg;
109 }
110
111 public static String explainFailedBasicAuthentication(OsmApiException e) {
112 e.printStackTrace();
113 return tr("<html>"
114 + "Authentication at the OSM server with the username ''{0}'' failed.<br>"
115 + "Please check the username and the password in the JOSM preferences."
116 + "</html>",
117 Main.pref.get("osm-server.username")
118 );
119 }
120
121 public static String explainFailedOAuthAuthentication(OsmApiException e) {
122 e.printStackTrace();
123 return tr("<html>"
124 + "Authentication at the OSM server with the OAuth token ''{0}'' failed.<br>"
125 + "Please launch the preferences dialog and retrieve another OAuth token."
126 + "</html>",
127 OAuthAccessTokenHolder.getInstance().getAccessTokenKey()
128 );
129 }
130
131 public static String explainFailedAuthorisation(OsmApiException e) {
132 e.printStackTrace();
133 String header = e.getErrorHeader();
134 String body = e.getErrorBody();
135 if (body.equals("Your access to the API is temporarily suspended. Please log-in to the web interface to view the Contributor Terms. You do not need to agree, but you must view them.")) {
136 return tr("<html>"
137 +"Your access to the API is temporarily suspended.<br>"
138 + "Please log-in to the web interface to view the Contributor Terms.<br>"
139 + "You do not need to agree, but you must view them."
140 + "</html>");
141 }
142 String msg = null;
143 if (header != null) {
144 if (body != null && !header.equals(body)) {
145 msg = header + " (" + body + ")";
146 } else {
147 msg = header;
148 }
149 } else {
150 msg = body;
151 }
152
153 return tr("<html>"
154 + "Authorisation at the OSM server failed.<br>"
155 + "The server reported the following error:<br>"
156 + "''{0}''"
157 + "</html>",
158 msg
159 );
160 }
161
162 public static String explainFailedOAuthAuthorisation(OsmApiException e) {
163 e.printStackTrace();
164 return tr("<html>"
165 + "Authorisation at the OSM server with the OAuth token ''{0}'' failed.<br>"
166 + "The token is not authorised to access the protected resource<br>"
167 + "''{1}''.<br>"
168 + "Please launch the preferences dialog and retrieve another OAuth token."
169 + "</html>",
170 OAuthAccessTokenHolder.getInstance().getAccessTokenKey(),
171 e.getAccessedUrl() == null ? tr("unknown") : e.getAccessedUrl()
172 );
173 }
174
175 /**
176 * Explains an OSM API exception because of a client timeout (HTTP 408).
177 *
178 * @param e the exception
179 * @return the message
180 */
181 public static String explainClientTimeout(OsmApiException e) {
182 e.printStackTrace();
183 return tr("<html>"
184 + "Communication with the OSM server ''{0}'' timed out. Please retry later."
185 + "</html>",
186 OsmApi.getOsmApi().getBaseUrl()
187 );
188 }
189
190 /**
191 * Replies a generic error message for an OSM API exception
192 *
193 * @param e the exception
194 * @return the message
195 */
196 public static String explainGenericOsmApiException(OsmApiException e) {
197 e.printStackTrace();
198 String errMsg = e.getErrorHeader();
199 if (errMsg == null) {
200 errMsg = e.getErrorBody();
201 }
202 if (errMsg == null) {
203 errMsg = tr("no error message available");
204 }
205 return tr("<html>"
206 + "Communication with the OSM server ''{0}''failed. The server replied<br>"
207 + "the following error code and the following error message:<br>"
208 + "<strong>Error code:<strong> {1}<br>"
209 + "<strong>Error message (untranslated)</strong>: {2}"
210 + "</html>",
211 OsmApi.getOsmApi().getBaseUrl(),
212 e.getResponseCode(),
213 errMsg
214 );
215 }
216
217 /**
218 * Explains an error due to a 409 conflict
219 *
220 * @param e the exception
221 */
222 public static String explainConflict(OsmApiException e) {
223 e.printStackTrace();
224 String msg = e.getErrorHeader();
225 if (msg != null) {
226 String pattern = "The changeset (\\d+) was closed at (.*)";
227 Pattern p = Pattern.compile(pattern);
228 Matcher m = p.matcher(msg);
229 if (m.matches()) {
230 long changesetId = Long.parseLong(m.group(1));
231 // Example: "2010-09-07 14:39:41 UTC". Always parsed with US locale, regardless
232 // of the current locale in JOSM
233 DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US);
234 Date closeDate = null;
235 try {
236 closeDate = formatter.parse(m.group(2));
237 } catch(ParseException ex) {
238 System.err.println(tr("Failed to parse date ''{0}'' replied by server.", m.group(2)));
239 ex.printStackTrace();
240 }
241 if (closeDate == null) {
242 msg = tr(
243 "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.</html>",
244 changesetId
245 );
246 } else {
247 SimpleDateFormat dateFormat = new SimpleDateFormat();
248 msg = tr(
249 "<html>Closing of changeset <strong>{0}</strong> failed<br>"
250 +" because it has already been closed on {1}.</html>",
251 changesetId,
252 dateFormat.format(closeDate)
253 );
254 }
255 return msg;
256 }
257 msg = tr(
258 "<html>The server reported that it has detected a conflict.<br>" +
259 "Error message (untranslated):<br>{0}</html>",
260 msg
261 );
262 }
263 msg = tr(
264 "<html>The server reported that it has detected a conflict.</html>"
265 );
266 return msg;
267 }
268
269 /**
270 * Explains an exception thrown during upload because the changeset which data is
271 * uploaded to is already closed.
272 *
273 * @param e the exception
274 */
275 public static String explainChangesetClosedException(ChangesetClosedException e) {
276 String msg;
277 SimpleDateFormat dateFormat = new SimpleDateFormat();
278 msg = tr(
279 "<html>Failed to upload to changeset <strong>{0}</strong><br>"
280 +"because it has already been closed on {1}.</html>",
281 e.getChangesetId(),
282 e.getClosedOn() == null ? "?" : dateFormat.format(e.getClosedOn())
283 );
284 e.printStackTrace();
285 return msg;
286 }
287
288 /**
289 * Explains an exception with a generic message dialog
290 *
291 * @param e the exception
292 */
293 public static String explainGeneric(Exception e) {
294 String msg = e.getMessage();
295 if (msg == null || msg.trim().equals("")) {
296 msg = e.toString();
297 }
298 e.printStackTrace();
299 return msg;
300 }
301
302 /**
303 * Explains a {@see SecurityException} which has caused an {@see OsmTransferException}.
304 * This is most likely happening when user tries to access the OSM API from within an
305 * applet which wasn't loaded from the API server.
306 *
307 * @param e the exception
308 */
309
310 public static String explainSecurityException(OsmTransferException e) {
311 String apiUrl = e.getUrl();
312 String host = tr("unknown");
313 try {
314 host = new URL(apiUrl).getHost();
315 } catch (MalformedURLException ex) {
316 // shouldn't happen
317 }
318
319 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>"
320 + "for security reasons. This is most likely because you are running<br>"
321 + "in an applet and because you did not load your applet from ''{1}''.</html>", apiUrl, host);
322 return message;
323 }
324
325 /**
326 * Explains a {@see SocketException} which has caused an {@see OsmTransferException}.
327 * This is most likely because there's not connection to the Internet or because
328 * the remote server is not reachable.
329 *
330 * @param e the exception
331 */
332
333 public static String explainNestedSocketException(OsmTransferException e) {
334 String apiUrl = e.getUrl();
335 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
336 + "Please check your internet connection.</html>", apiUrl);
337 e.printStackTrace();
338 return message;
339 }
340
341 /**
342 * Explains a {@see IOException} which has caused an {@see OsmTransferException}.
343 * This is most likely happening when the communication with the remote server is
344 * interrupted for any reason.
345 *
346 * @param e the exception
347 */
348
349 public static String explainNestedIOException(OsmTransferException e) {
350 IOException ioe = getNestedException(e, IOException.class);
351 String apiUrl = e.getUrl();
352 String message = tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>"
353 + "due to a problem with transferring data.<br>" + "Details(untranslated): {1}</html>", apiUrl, ioe
354 .getMessage());
355 e.printStackTrace();
356 return message;
357 }
358
359 /**
360 * Explains a {@see IllegalDataException} which has caused an {@see OsmTransferException}.
361 * This is most likely happening when JOSM tries to load data in in an unsupported format.
362 *
363 * @param e the exception
364 */
365 public static String explainNestedIllegalDataException(OsmTransferException e) {
366 IllegalDataException ide = getNestedException(e, IllegalDataException.class);
367 String message = tr("<html>Failed to download data. "
368 + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>"
369 + "<br>Details (untranslated): {0}</html>", ide.getMessage());
370 e.printStackTrace();
371 return message;
372 }
373
374 /**
375 * Explains a {@see OsmApiException} which was thrown because of an internal server
376 * error in the OSM API server..
377 *
378 * @param e the exception
379 */
380
381 public static String explainInternalServerError(OsmTransferException e) {
382 String apiUrl = e.getUrl();
383 String message = tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>"
384 + "This is most likely a temporary problem. Please try again later.</html>", apiUrl);
385 e.printStackTrace();
386 return message;
387 }
388
389 /**
390 * Explains a {@see OsmApiException} which was thrown because of a bad
391 * request
392 *
393 * @param e the exception
394 */
395 public static String explainBadRequest(OsmApiException e) {
396 String apiUrl = OsmApi.getOsmApi().getBaseUrl();
397 String message = tr("The OSM server ''{0}'' reported a bad request.<br>", apiUrl);
398 if (e.getErrorHeader() != null &&
399 (e.getErrorHeader().startsWith("The maximum bbox") ||
400 e.getErrorHeader().startsWith("You requested too many nodes"))) {
401 message += "<br>"
402 + tr("The area you tried to download is too big or your request was too large."
403 + "<br>Either request a smaller area or use an export file provided by the OSM community.");
404 } else if (e.getErrorHeader() != null) {
405 message += tr("<br>Error message(untranslated): {0}", e.getErrorHeader());
406 }
407 message = "<html>" + message + "</html>";
408 e.printStackTrace();
409 return message;
410 }
411
412 /**
413 * Explains a {@see OsmApiException} which was thrown because a resource wasn't found.
414 *
415 * @param e the exception
416 */
417 public static String explainNotFound(OsmApiException e) {
418 String apiUrl = OsmApi.getOsmApi().getBaseUrl();
419 String message = tr("The OSM server ''{0}'' does not know about an object<br>"
420 + "you tried to read, update, or delete. Either the respective object<br>"
421 + "does not exist on the server or you are using an invalid URL to access<br>"
422 + "it. Please carefully check the server''s address ''{0}'' for typos."
423 , apiUrl);
424 message = "<html>" + message + "</html>";
425 e.printStackTrace();
426 return message;
427 }
428
429 /**
430 * Explains a {@see UnknownHostException} which has caused an {@see OsmTransferException}.
431 * This is most likely happening when there is an error in the API URL or when
432 * local DNS services are not working.
433 *
434 * @param e the exception
435 */
436
437 public static String explainNestedUnknownHostException(OsmTransferException e) {
438 String apiUrl = e.getUrl();
439 String host = tr("unknown");
440 try {
441 host = new URL(apiUrl).getHost();
442 } catch (MalformedURLException ex) {
443 // shouldn't happen
444 }
445
446 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>"
447 + "Host name ''{1}'' could not be resolved. <br>"
448 + "Please check the API URL in your preferences and your internet connection.</html>", apiUrl, host);
449 e.printStackTrace();
450 return message;
451 }
452
453 /**
454 * Replies the first nested exception of type <code>nestedClass</code> (including
455 * the root exception <code>e</code>) or null, if no such exception is found.
456 *
457 * @param <T>
458 * @param e the root exception
459 * @param nestedClass the type of the nested exception
460 * @return the first nested exception of type <code>nestedClass</code> (including
461 * the root exception <code>e</code>) or null, if no such exception is found.
462 */
463 protected static <T> T getNestedException(Exception e, Class<T> nestedClass) {
464 Throwable t = e;
465 while (t != null && !(nestedClass.isInstance(t))) {
466 t = t.getCause();
467 }
468 if (t == null)
469 return null;
470 else if (nestedClass.isInstance(t))
471 return nestedClass.cast(t);
472 return null;
473 }
474
475 /**
476 * Explains an {@see OsmTransferException} to the user.
477 *
478 * @param e the {@see OsmTransferException}
479 */
480 public static String explainOsmTransferException(OsmTransferException e) {
481 if (getNestedException(e, SecurityException.class) != null)
482 return explainSecurityException(e);
483 if (getNestedException(e, SocketException.class) != null)
484 return explainNestedSocketException(e);
485 if (getNestedException(e, UnknownHostException.class) != null)
486 return explainNestedUnknownHostException(e);
487 if (getNestedException(e, IOException.class) != null)
488 return explainNestedIOException(e);
489 if (e instanceof OsmApiInitializationException)
490 return explainOsmApiInitializationException((OsmApiInitializationException) e);
491
492 if (e instanceof ChangesetClosedException)
493 return explainChangesetClosedException((ChangesetClosedException)e);
494
495 if (e instanceof OsmApiException) {
496 OsmApiException oae = (OsmApiException) e;
497 if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED)
498 return explainPreconditionFailed(oae);
499 if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE)
500 return explainGoneForUnknownPrimitive(oae);
501 if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR)
502 return explainInternalServerError(oae);
503 if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST)
504 return explainBadRequest(oae);
505 }
506 return explainGeneric(e);
507 }
508
509 /**
510 * explains the case of an error due to a delete request on an already deleted
511 * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which
512 * {@see OsmPrimitive} is causing the error.
513 *
514 * @param e the exception
515 */
516 public static String explainGoneForUnknownPrimitive(OsmApiException e) {
517 String msg = tr(
518 "<html>The server reports that an object is deleted.<br>"
519 + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> "
520 + "<strong>Downloading failed</strong> if you tried to download this object.<br>"
521 + "<br>"
522 + "The error message is:<br>" + "{0}"
523 + "</html>", e.getMessage().replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"));
524 return msg;
525
526 }
527
528 /**
529 * Explains an {@see Exception} to the user.
530 *
531 * @param e the {@see Exception}
532 */
533 public static String explainException(Exception e) {
534 String msg = "";
535 if (e instanceof OsmTransferException) {
536 msg = explainOsmTransferException((OsmTransferException) e);
537 } else {
538 msg = explainGeneric(e);
539 }
540 e.printStackTrace();
541 return msg;
542 }
543}
Note: See TracBrowser for help on using the repository browser.