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

Last change on this file since 7058 was 7037, checked in by Don-vip, 10 years ago

see #8465 - last batch of try-with-resources

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