source: josm/trunk/src/org/openstreetmap/josm/io/OsmApi.java@ 3965

Last change on this file since 3965 was 3934, checked in by framm, 13 years ago

Changed the way in which JOSM handles imagery layer blacklisting. Instead
of a hard-coded list of Google URLs, we now parse the server's
/api/capabilities response which is expected to look like this:

<osm version="0.6" generator="OpenStreetMap server">

<api>

<version minimum="0.6" maximum="0.6"/>
<area maximum="0.25"/>
<tracepoints per_page="5000"/>
<waynodes maximum="2000"/>
<changesets maximum_elements="50000"/>
<timeout seconds="300"/>

</api>
<policy>

<imagery>

<blacklist regex=".*\.google\.com/.*"/>
<blacklist regex=".*209\.85\.2\d\d.*"/>
<blacklist regex=".*209\.85\.1[3-9]\d.*"/>
<blacklist regex=".*209\.85\.12[89].*"/>

</imagery>

</policy>

</osm>

JOSM will now try to establish an API connection when started, so that
it knows about blacklisted layers. It will also re-read the list when
the URL is changed in the preferences, and if any prohibited layers are
active they will be removed.

For an interim period, JOSM still uses the four regular expressions
listed above whenever the server API URL is *.openstreetmap.org and
the API does not send any blacklist entries. It is expected that the
API will soon return proper blacklist entries.

Things that could be improved:

  1. Establish a general listener system where components can register

their interest in a change of the configured OSM server. Currently
we have to plug through to the gui layer (Main.main.mapView) from
the base comms layer (OsmApi) which is ugly.

  1. Establish a new class of blacklist which works by IP number and not

just regular expression, so that you can say "any host name that resolves
to this IP is blacklisted".

  1. Track all layers that were used in editing a data set, and refuse

uploading if the upload URL bans any of these layers.

  • Property svn:eol-style set to native
File size: 29.1 KB
Line 
1//License: GPL. See README for details.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.io.BufferedReader;
8import java.io.BufferedWriter;
9import java.io.IOException;
10import java.io.InputStream;
11import java.io.InputStreamReader;
12import java.io.OutputStream;
13import java.io.OutputStreamWriter;
14import java.io.PrintWriter;
15import java.io.StringReader;
16import java.io.StringWriter;
17import java.net.ConnectException;
18import java.net.HttpURLConnection;
19import java.net.SocketTimeoutException;
20import java.net.URL;
21import java.net.UnknownHostException;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.HashMap;
25import java.util.logging.Logger;
26
27import javax.xml.parsers.ParserConfigurationException;
28import javax.xml.parsers.SAXParserFactory;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.osm.Changeset;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
34import org.openstreetmap.josm.gui.layer.Layer;
35import org.openstreetmap.josm.gui.layer.ImageryLayer;
36import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
37import org.openstreetmap.josm.gui.progress.ProgressMonitor;
38import org.openstreetmap.josm.tools.CheckParameterUtil;
39import org.xml.sax.Attributes;
40import org.xml.sax.InputSource;
41import org.xml.sax.SAXException;
42import org.xml.sax.helpers.DefaultHandler;
43
44/**
45 * Class that encapsulates the communications with the OSM API.
46 *
47 * All interaction with the server-side OSM API should go through this class.
48 *
49 * It is conceivable to extract this into an interface later and create various
50 * classes implementing the interface, to be able to talk to various kinds of servers.
51 *
52 */
53public class OsmApi extends OsmConnection {
54 @SuppressWarnings("unused")
55 static private final Logger logger = Logger.getLogger(OsmApi.class.getName());
56 /** max number of retries to send a request in case of HTTP 500 errors or timeouts */
57 static public final int DEFAULT_MAX_NUM_RETRIES = 5;
58
59 /** the collection of instantiated OSM APIs */
60 private static HashMap<String, OsmApi> instances = new HashMap<String, OsmApi>();
61
62 /**
63 * replies the {@see OsmApi} for a given server URL
64 *
65 * @param serverUrl the server URL
66 * @return the OsmApi
67 * @throws IllegalArgumentException thrown, if serverUrl is null
68 *
69 */
70 static public OsmApi getOsmApi(String serverUrl) {
71 OsmApi api = instances.get(serverUrl);
72 if (api == null) {
73 api = new OsmApi(serverUrl);
74 instances.put(serverUrl,api);
75 }
76 return api;
77 }
78 /**
79 * replies the {@see OsmApi} for the URL given by the preference <code>osm-server.url</code>
80 *
81 * @return the OsmApi
82 * @exception IllegalStateException thrown, if the preference <code>osm-server.url</code> is not set
83 *
84 */
85 static public OsmApi getOsmApi() {
86 String serverUrl = Main.pref.get("osm-server.url", "http://api.openstreetmap.org/api");
87 if (serverUrl == null)
88 throw new IllegalStateException(tr("Preference ''{0}'' missing. Cannot initialize OsmApi.", "osm-server.url"));
89 return getOsmApi(serverUrl);
90 }
91
92 /** the server URL */
93 private String serverUrl;
94
95 /**
96 * Object describing current changeset
97 */
98 private Changeset changeset;
99
100 /**
101 * API version used for server communications
102 */
103 private String version = null;
104
105 /** the api capabilities */
106 private Capabilities capabilities = new Capabilities();
107
108 /**
109 * true if successfully initialized
110 */
111 private boolean initialized = false;
112
113 private StringWriter swriter = new StringWriter();
114 private OsmWriter osmWriter = new OsmWriter(new PrintWriter(swriter), true, null);
115
116 /**
117 * A parser for the "capabilities" response XML
118 */
119 private class CapabilitiesParser extends DefaultHandler {
120 @Override
121 public void startDocument() throws SAXException {
122 capabilities.clear();
123 }
124
125 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
126 for (int i=0; i< atts.getLength(); i++) {
127 capabilities.put(qName, atts.getQName(i), atts.getValue(i));
128 }
129 }
130 }
131
132 /**
133 * creates an OSM api for a specific server URL
134 *
135 * @param serverUrl the server URL. Must not be null
136 * @exception IllegalArgumentException thrown, if serverUrl is null
137 */
138 protected OsmApi(String serverUrl) {
139 CheckParameterUtil.ensureParameterNotNull(serverUrl, "serverUrl");
140 this.serverUrl = serverUrl;
141 }
142
143 /**
144 * Returns the OSM protocol version we use to talk to the server.
145 * @return protocol version, or null if not yet negotiated.
146 */
147 public String getVersion() {
148 return version;
149 }
150
151 /**
152 * Initializes this component by negotiating a protocol version with the server.
153 *
154 * @exception OsmApiInitializationException thrown, if an exception occurs
155 */
156 public void initialize(ProgressMonitor monitor) throws OsmApiInitializationException, OsmTransferCancelledException {
157 if (initialized)
158 return;
159 cancel = false;
160 try {
161 String s = sendRequest("GET", "capabilities", null, monitor, false);
162 InputSource inputSource = new InputSource(new StringReader(s));
163 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new CapabilitiesParser());
164 if (capabilities.supportsVersion("0.6")) {
165 version = "0.6";
166 } else {
167 System.err.println(tr("This version of JOSM is incompatible with the configured server."));
168 System.err.println(tr("It supports protocol version 0.6, while the server says it supports {0} to {1}.",
169 capabilities.get("version", "minimum"), capabilities.get("version", "maximum")));
170 initialized = false;
171 }
172 System.out.println(tr("Communications with {0} established using protocol version {1}.",
173 serverUrl,
174 version));
175 osmWriter.setVersion(version);
176 initialized = true;
177
178 /* This is an interim solution for openstreetmap.org not currently
179 * transmitting their imagery blacklist in the capabilities call.
180 * remove this as soon as openstreetmap.org adds blacklists. */
181 if (this.serverUrl.matches(".*openstreetmap.org/api.*") && capabilities.getImageryBlacklist().isEmpty())
182 {
183 capabilities.put("blacklist", "regex", ".*\\.google\\.com/.*");
184 capabilities.put("blacklist", "regex", ".*209\\.85\\.2\\d\\d.*");
185 capabilities.put("blacklist", "regex", ".*209\\.85\\.1[3-9]\\d.*");
186 capabilities.put("blacklist", "regex", ".*209\\.85\\.12[89].*");
187 }
188
189 /* This checks if there are any layers currently displayed that
190 * are now on the blacklist, and removes them. This is a rare
191 * situaton - probably only occurs if the user changes the API URL
192 * in the preferences menu. Otherwise they would not have been able
193 * to load the layers in the first place becuase they would have
194 * been disabled! */
195 if (Main.main.isDisplayingMapView()) {
196 for (Layer l : Main.map.mapView.getLayersOfType(ImageryLayer.class)) {
197 if (((ImageryLayer) l).getInfo().isBlacklisted()) {
198 System.out.println(tr("Removed layer {0} because it is not allowed by the configured API.", l.getName()));
199 Main.main.removeLayer(l);
200 }
201 }
202 }
203
204 } catch(IOException e) {
205 initialized = false;
206 throw new OsmApiInitializationException(e);
207 } catch(SAXException e) {
208 initialized = false;
209 throw new OsmApiInitializationException(e);
210 } catch(ParserConfigurationException e) {
211 initialized = false;
212 throw new OsmApiInitializationException(e);
213 } catch(OsmTransferCancelledException e){
214 throw e;
215 } catch(OsmTransferException e) {
216 initialized = false;
217 throw new OsmApiInitializationException(e);
218 }
219 }
220
221 /**
222 * Makes an XML string from an OSM primitive. Uses the OsmWriter class.
223 * @param o the OSM primitive
224 * @param addBody true to generate the full XML, false to only generate the encapsulating tag
225 * @return XML string
226 */
227 private String toXml(OsmPrimitive o, boolean addBody) {
228 swriter.getBuffer().setLength(0);
229 osmWriter.setWithBody(addBody);
230 osmWriter.setChangeset(changeset);
231 osmWriter.header();
232 o.visit(osmWriter);
233 osmWriter.footer();
234 osmWriter.out.flush();
235 return swriter.toString();
236 }
237
238 /**
239 * Makes an XML string from an OSM primitive. Uses the OsmWriter class.
240 * @param o the OSM primitive
241 * @param addBody true to generate the full XML, false to only generate the encapsulating tag
242 * @return XML string
243 */
244 private String toXml(Changeset s) {
245 swriter.getBuffer().setLength(0);
246 osmWriter.header();
247 s.visit(osmWriter);
248 osmWriter.footer();
249 osmWriter.out.flush();
250 return swriter.toString();
251 }
252
253 /**
254 * Returns the base URL for API requests, including the negotiated version number.
255 * @return base URL string
256 */
257 public String getBaseUrl() {
258 StringBuffer rv = new StringBuffer(serverUrl);
259 if (version != null) {
260 rv.append("/");
261 rv.append(version);
262 }
263 rv.append("/");
264 // this works around a ruby (or lighttpd) bug where two consecutive slashes in
265 // an URL will cause a "404 not found" response.
266 int p; while ((p = rv.indexOf("//", 6)) > -1) { rv.delete(p, p + 1); }
267 return rv.toString();
268 }
269
270 /**
271 * Creates an OSM primitive on the server. The OsmPrimitive object passed in
272 * is modified by giving it the server-assigned id.
273 *
274 * @param osm the primitive
275 * @throws OsmTransferException if something goes wrong
276 */
277 public void createPrimitive(OsmPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
278 String ret = "";
279 try {
280 ensureValidChangeset();
281 initialize(monitor);
282 ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/create", toXml(osm, true),monitor);
283 osm.setOsmId(Long.parseLong(ret.trim()), 1);
284 osm.setChangesetId(getChangeset().getId());
285 } catch(NumberFormatException e){
286 throw new OsmTransferException(tr("Unexpected format of ID replied by the server. Got ''{0}''.", ret));
287 }
288 }
289
290 /**
291 * Modifies an OSM primitive on the server.
292 *
293 * @param osm the primitive. Must not be null.
294 * @param monitor the progress monitor
295 * @throws OsmTransferException if something goes wrong
296 */
297 public void modifyPrimitive(OsmPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
298 String ret = null;
299 try {
300 ensureValidChangeset();
301 initialize(monitor);
302 // normal mode (0.6 and up) returns new object version.
303 ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.getId(), toXml(osm, true), monitor);
304 osm.setOsmId(osm.getId(), Integer.parseInt(ret.trim()));
305 osm.setChangesetId(getChangeset().getId());
306 osm.setVisible(true);
307 } catch(NumberFormatException e) {
308 throw new OsmTransferException(tr("Unexpected format of new version of modified primitive ''{0}''. Got ''{1}''.", osm.getId(), ret));
309 }
310 }
311
312 /**
313 * Deletes an OSM primitive on the server.
314 * @param osm the primitive
315 * @throws OsmTransferException if something goes wrong
316 */
317 public void deletePrimitive(OsmPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
318 ensureValidChangeset();
319 initialize(monitor);
320 // can't use a the individual DELETE method in the 0.6 API. Java doesn't allow
321 // submitting a DELETE request with content, the 0.6 API requires it, however. Falling back
322 // to diff upload.
323 //
324 uploadDiff(Collections.singleton(osm), monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
325 }
326
327 /**
328 * Creates a new changeset based on the keys in <code>changeset</code>. If this
329 * method succeeds, changeset.getId() replies the id the server assigned to the new
330 * changeset
331 *
332 * The changeset must not be null, but its key/value-pairs may be empty.
333 *
334 * @param changeset the changeset toe be created. Must not be null.
335 * @param progressMonitor the progress monitor
336 * @throws OsmTransferException signifying a non-200 return code, or connection errors
337 * @throws IllegalArgumentException thrown if changeset is null
338 */
339 public void openChangeset(Changeset changeset, ProgressMonitor progressMonitor) throws OsmTransferException {
340 CheckParameterUtil.ensureParameterNotNull(changeset, "changeset");
341 try {
342 progressMonitor.beginTask((tr("Creating changeset...")));
343 initialize(progressMonitor);
344 String ret = "";
345 try {
346 ret = sendRequest("PUT", "changeset/create", toXml(changeset),progressMonitor);
347 changeset.setId(Integer.parseInt(ret.trim()));
348 changeset.setOpen(true);
349 } catch(NumberFormatException e){
350 throw new OsmTransferException(tr("Unexpected format of ID replied by the server. Got ''{0}''.", ret));
351 }
352 progressMonitor.setCustomText((tr("Successfully opened changeset {0}",changeset.getId())));
353 } finally {
354 progressMonitor.finishTask();
355 }
356 }
357
358 /**
359 * Updates a changeset with the keys in <code>changesetUpdate</code>. The changeset must not
360 * be null and id > 0 must be true.
361 *
362 * @param changeset the changeset to update. Must not be null.
363 * @param monitor the progress monitor. If null, uses the {@see NullProgressMonitor#INSTANCE}.
364 *
365 * @throws OsmTransferException if something goes wrong.
366 * @throws IllegalArgumentException if changeset is null
367 * @throws IllegalArgumentException if changeset.getId() <= 0
368 *
369 */
370 public void updateChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException {
371 CheckParameterUtil.ensureParameterNotNull(changeset, "changeset");
372 if (monitor == null) {
373 monitor = NullProgressMonitor.INSTANCE;
374 }
375 if (changeset.getId() <= 0)
376 throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId()));
377 try {
378 monitor.beginTask(tr("Updating changeset..."));
379 initialize(monitor);
380 monitor.setCustomText(tr("Updating changeset {0}...", changeset.getId()));
381 sendRequest(
382 "PUT",
383 "changeset/" + changeset.getId(),
384 toXml(changeset),
385 monitor
386 );
387 } catch(ChangesetClosedException e) {
388 e.setSource(ChangesetClosedException.Source.UPDATE_CHANGESET);
389 throw e;
390 } catch(OsmApiException e) {
391 if (e.getResponseCode() == HttpURLConnection.HTTP_CONFLICT && ChangesetClosedException.errorHeaderMatchesPattern(e.getErrorHeader()))
392 throw new ChangesetClosedException(e.getErrorHeader(), ChangesetClosedException.Source.UPDATE_CHANGESET);
393 throw e;
394 } finally {
395 monitor.finishTask();
396 }
397 }
398
399 /**
400 * Closes a changeset on the server. Sets changeset.setOpen(false) if this operation
401 * succeeds.
402 *
403 * @param changeset the changeset to be closed. Must not be null. changeset.getId() > 0 required.
404 * @param monitor the progress monitor. If null, uses {@see NullProgressMonitor#INSTANCE}
405 *
406 * @throws OsmTransferException if something goes wrong.
407 * @throws IllegalArgumentException thrown if changeset is null
408 * @throws IllegalArgumentException thrown if changeset.getId() <= 0
409 */
410 public void closeChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException {
411 CheckParameterUtil.ensureParameterNotNull(changeset, "changeset");
412 if (monitor == null) {
413 monitor = NullProgressMonitor.INSTANCE;
414 }
415 if (changeset.getId() <= 0)
416 throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId()));
417 try {
418 monitor.beginTask(tr("Closing changeset..."));
419 initialize(monitor);
420 /* send "\r\n" instead of empty string, so we don't send zero payload - works around bugs
421 in proxy software */
422 sendRequest("PUT", "changeset" + "/" + changeset.getId() + "/close", "\r\n", monitor);
423 changeset.setOpen(false);
424 } finally {
425 monitor.finishTask();
426 }
427 }
428
429 /**
430 * Uploads a list of changes in "diff" form to the server.
431 *
432 * @param list the list of changed OSM Primitives
433 * @param monitor the progress monitor
434 * @return list of processed primitives
435 * @throws OsmTransferException if something is wrong
436 */
437 public Collection<OsmPrimitive> uploadDiff(Collection<OsmPrimitive> list, ProgressMonitor monitor) throws OsmTransferException {
438 try {
439 monitor.beginTask("", list.size() * 2);
440 if (changeset == null)
441 throw new OsmTransferException(tr("No changeset present for diff upload."));
442
443 initialize(monitor);
444
445 // prepare upload request
446 //
447 OsmChangeBuilder changeBuilder = new OsmChangeBuilder(changeset);
448 monitor.subTask(tr("Preparing upload request..."));
449 changeBuilder.start();
450 changeBuilder.append(list);
451 changeBuilder.finish();
452 String diffUploadRequest = changeBuilder.getDocument();
453
454 // Upload to the server
455 //
456 monitor.indeterminateSubTask(
457 trn("Uploading {0} object...", "Uploading {0} objects...", list.size(), list.size()));
458 String diffUploadResponse = sendRequest("POST", "changeset/" + changeset.getId() + "/upload", diffUploadRequest,monitor);
459
460 // Process the response from the server
461 //
462 DiffResultProcessor reader = new DiffResultProcessor(list);
463 reader.parse(diffUploadResponse, monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
464 return reader.postProcess(
465 getChangeset(),
466 monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)
467 );
468 } catch(OsmTransferException e) {
469 throw e;
470 } catch(OsmDataParsingException e) {
471 throw new OsmTransferException(e);
472 } finally {
473 monitor.finishTask();
474 }
475 }
476
477 private void sleepAndListen(int retry, ProgressMonitor monitor) throws OsmTransferCancelledException {
478 System.out.print(tr("Waiting 10 seconds ... "));
479 for(int i=0; i < 10; i++) {
480 if (monitor != null) {
481 monitor.setCustomText(tr("Starting retry {0} of {1} in {2} seconds ...", getMaxRetries() - retry,getMaxRetries(), 10-i));
482 }
483 if (cancel)
484 throw new OsmTransferCancelledException();
485 try {
486 Thread.sleep(1000);
487 } catch (InterruptedException ex) {}
488 }
489 System.out.println(tr("OK - trying again."));
490 }
491
492 /**
493 * Replies the max. number of retries in case of 5XX errors on the server
494 *
495 * @return the max number of retries
496 */
497 protected int getMaxRetries() {
498 int ret = Main.pref.getInteger("osm-server.max-num-retries", DEFAULT_MAX_NUM_RETRIES);
499 return Math.max(ret,0);
500 }
501
502 protected boolean isUsingOAuth() {
503 String authMethod = Main.pref.get("osm-server.auth-method", "basic");
504 return authMethod.equals("oauth");
505 }
506
507 private String sendRequest(String requestMethod, String urlSuffix,String requestBody, ProgressMonitor monitor) throws OsmTransferException {
508 return sendRequest(requestMethod, urlSuffix, requestBody, monitor, true);
509 }
510
511 /**
512 * Generic method for sending requests to the OSM API.
513 *
514 * This method will automatically re-try any requests that are answered with a 5xx
515 * error code, or that resulted in a timeout exception from the TCP layer.
516 *
517 * @param requestMethod The http method used when talking with the server.
518 * @param urlSuffix The suffix to add at the server url, not including the version number,
519 * but including any object ids (e.g. "/way/1234/history").
520 * @param requestBody the body of the HTTP request, if any.
521 * @param monitor the progress monitor
522 * @param doAuthenticate set to true, if the request sent to the server shall include authentication
523 * credentials;
524 *
525 * @return the body of the HTTP response, if and only if the response code was "200 OK".
526 * @exception OsmTransferException if the HTTP return code was not 200 (and retries have
527 * been exhausted), or rewrapping a Java exception.
528 */
529 private String sendRequest(String requestMethod, String urlSuffix,String requestBody, ProgressMonitor monitor, boolean doAuthenticate) throws OsmTransferException {
530 StringBuffer responseBody = new StringBuffer();
531 int retries = getMaxRetries();
532
533 while(true) { // the retry loop
534 try {
535 URL url = new URL(new URL(getBaseUrl()), urlSuffix);
536 System.out.print(requestMethod + " " + url + "... ");
537 activeConnection = (HttpURLConnection)url.openConnection();
538 activeConnection.setConnectTimeout(15000);
539 activeConnection.setRequestMethod(requestMethod);
540 if (doAuthenticate) {
541 addAuth(activeConnection);
542 }
543
544 if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) {
545 activeConnection.setDoOutput(true);
546 activeConnection.setRequestProperty("Content-type", "text/xml");
547 OutputStream out = activeConnection.getOutputStream();
548
549 // It seems that certain bits of the Ruby API are very unhappy upon
550 // receipt of a PUT/POST message without a Content-length header,
551 // even if the request has no payload.
552 // Since Java will not generate a Content-length header unless
553 // we use the output stream, we create an output stream for PUT/POST
554 // even if there is no payload.
555 if (requestBody != null) {
556 BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
557 bwr.write(requestBody);
558 bwr.flush();
559 }
560 out.close();
561 }
562
563 activeConnection.connect();
564 System.out.println(activeConnection.getResponseMessage());
565 int retCode = activeConnection.getResponseCode();
566
567 if (retCode >= 500) {
568 if (retries-- > 0) {
569 sleepAndListen(retries, monitor);
570 System.out.println(tr("Starting retry {0} of {1}.", getMaxRetries() - retries,getMaxRetries()));
571 continue;
572 }
573 }
574
575 // populate return fields.
576 responseBody.setLength(0);
577
578 // If the API returned an error code like 403 forbidden, getInputStream
579 // will fail with an IOException.
580 InputStream i = null;
581 try {
582 i = activeConnection.getInputStream();
583 } catch (IOException ioe) {
584 i = activeConnection.getErrorStream();
585 }
586 if (i != null) {
587 // the input stream can be null if both the input and the error stream
588 // are null. Seems to be the case if the OSM server replies a 401
589 // Unauthorized, see #3887.
590 //
591 BufferedReader in = new BufferedReader(new InputStreamReader(i));
592 String s;
593 while((s = in.readLine()) != null) {
594 responseBody.append(s);
595 responseBody.append("\n");
596 }
597 }
598 String errorHeader = null;
599 // Look for a detailed error message from the server
600 if (activeConnection.getHeaderField("Error") != null) {
601 errorHeader = activeConnection.getHeaderField("Error");
602 System.err.println("Error header: " + errorHeader);
603 } else if (retCode != 200 && responseBody.length()>0) {
604 System.err.println("Error body: " + responseBody);
605 }
606 activeConnection.disconnect();
607
608 errorHeader = errorHeader == null? null : errorHeader.trim();
609 String errorBody = responseBody.length() == 0? null : responseBody.toString().trim();
610 switch(retCode) {
611 case HttpURLConnection.HTTP_OK:
612 return responseBody.toString();
613 case HttpURLConnection.HTTP_GONE:
614 throw new OsmApiPrimitiveGoneException(errorHeader, errorBody);
615 case HttpURLConnection.HTTP_CONFLICT:
616 if (ChangesetClosedException.errorHeaderMatchesPattern(errorHeader))
617 throw new ChangesetClosedException(errorBody, ChangesetClosedException.Source.UPLOAD_DATA);
618 else
619 throw new OsmApiException(retCode, errorHeader, errorBody);
620 case HttpURLConnection.HTTP_FORBIDDEN:
621 OsmApiException e = new OsmApiException(retCode, errorHeader, errorBody);
622 e.setAccessedUrl(activeConnection.getURL().toString());
623 throw e;
624 default:
625 throw new OsmApiException(retCode, errorHeader, errorBody);
626 }
627 } catch (UnknownHostException e) {
628 throw new OsmTransferException(e);
629 } catch (SocketTimeoutException e) {
630 if (retries-- > 0) {
631 continue;
632 }
633 throw new OsmTransferException(e);
634 } catch (ConnectException e) {
635 if (retries-- > 0) {
636 continue;
637 }
638 throw new OsmTransferException(e);
639 } catch(IOException e){
640 throw new OsmTransferException(e);
641 } catch(OsmTransferCancelledException e){
642 throw e;
643 } catch(OsmTransferException e) {
644 throw e;
645 }
646 }
647 }
648
649 /**
650 * returns the API capabilities; null, if the API is not initialized yet
651 *
652 * @return the API capabilities
653 */
654 public Capabilities getCapabilities() {
655 return capabilities;
656 }
657
658 /**
659 * Ensures that the current changeset can be used for uploading data
660 *
661 * @throws OsmTransferException thrown if the current changeset can't be used for
662 * uploading data
663 */
664 protected void ensureValidChangeset() throws OsmTransferException {
665 if (changeset == null)
666 throw new OsmTransferException(tr("Current changeset is null. Cannot upload data."));
667 if (changeset.getId() <= 0)
668 throw new OsmTransferException(tr("ID of current changeset > 0 required. Current ID is {0}.", changeset.getId()));
669 }
670 /**
671 * Replies the changeset data uploads are currently directed to
672 *
673 * @return the changeset data uploads are currently directed to
674 */
675 public Changeset getChangeset() {
676 return changeset;
677 }
678
679 /**
680 * Sets the changesets to which further data uploads are directed. The changeset
681 * can be null. If it isn't null it must have been created, i.e. id > 0 is required. Furthermore,
682 * it must be open.
683 *
684 * @param changeset the changeset
685 * @throws IllegalArgumentException thrown if changeset.getId() <= 0
686 * @throws IllegalArgumentException thrown if !changeset.isOpen()
687 */
688 public void setChangeset(Changeset changeset) {
689 if (changeset == null) {
690 this.changeset = null;
691 return;
692 }
693 if (changeset.getId() <= 0)
694 throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId()));
695 if (!changeset.isOpen())
696 throw new IllegalArgumentException(tr("Open changeset expected. Got closed changeset with id {0}.", changeset.getId()));
697 this.changeset = changeset;
698 }
699}
Note: See TracBrowser for help on using the repository browser.