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

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

Added three new hooks for plugins, allowing plugins to mess with JOSM's server I/O. Plugins can now add postprocessors to server reading (so they may change what JOSM receives from the OSM server), and they can add postprocessors to server writing (so they can react to an upload action *after* it happened and after IDs for new objects have been assigned - this is in addition to the existing UploadHook which is a server writing preprocessor). Thirdly, plugins can now substitute their own version of OsmWriter. Taken together, these changes make it possible for a plugin to introduce extra information from a non-OSM source into the JOSM editing process, and upon upload split away that extra information and send it to another destination. - Also made minor changes to CredentialDialog to allow plugins to subclass the dialog in case they need their own authentication.

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