source: osm/applications/editors/josm/plugins/sds/src/org/openstreetmap/hot/sds/SdsApi.java@ 31152

Last change on this file since 31152 was 31152, checked in by stoecker, 9 years ago

fix build failure

File size: 21.2 KB
Line 
1//License: GPL. See README for details.
2package org.openstreetmap.hot.sds;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedReader;
7import java.io.BufferedWriter;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.InputStreamReader;
11import java.io.OutputStream;
12import java.io.OutputStreamWriter;
13import java.net.ConnectException;
14import java.net.HttpURLConnection;
15import java.net.MalformedURLException;
16import java.net.SocketTimeoutException;
17import java.net.URL;
18import java.net.UnknownHostException;
19import java.util.HashMap;
20import java.util.List;
21import java.util.zip.GZIPInputStream;
22import java.util.zip.Inflater;
23import java.util.zip.InflaterInputStream;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.gui.progress.ProgressMonitor;
27import org.openstreetmap.josm.io.OsmApiException;
28import org.openstreetmap.josm.io.OsmTransferCanceledException;
29import org.openstreetmap.josm.io.ProgressInputStream;
30import org.openstreetmap.josm.tools.CheckParameterUtil;
31
32/**
33 * Class that encapsulates the communications with the SDS API.
34 *
35 * This is modeled after JOSM's own OsmAPI class.
36 *
37 */
38public class SdsApi extends SdsConnection {
39 /** max number of retries to send a request in case of HTTP 500 errors or timeouts */
40 static public final int DEFAULT_MAX_NUM_RETRIES = 5;
41
42 /** the collection of instantiated OSM APIs */
43 private static HashMap<String, SdsApi> instances = new HashMap<>();
44
45 /**
46 * replies the {@see OsmApi} for a given server URL
47 *
48 * @param serverUrl the server URL
49 * @return the OsmApi
50 * @throws IllegalArgumentException thrown, if serverUrl is null
51 *
52 */
53 static public SdsApi getSdsApi(String serverUrl) {
54 SdsApi api = instances.get(serverUrl);
55 if (api == null) {
56 api = new SdsApi(serverUrl);
57 instances.put(serverUrl,api);
58 }
59 return api;
60 }
61 /**
62 * replies the {@see OsmApi} for the URL given by the preference <code>sds-server.url</code>
63 *
64 * @return the OsmApi
65 *
66 */
67 static public SdsApi getSdsApi() {
68 String serverUrl = Main.pref.get("sds-server.url", "http://datastore.hotosm.org");
69 if (serverUrl == null)
70 throw new IllegalStateException(tr("Preference ''{0}'' missing. Cannot initialize SdsApi.", "sds-server.url"));
71 return getSdsApi(serverUrl);
72 }
73
74 /** the server URL */
75 private String serverUrl;
76
77 /**
78 * API version used for server communications
79 */
80 private String version = null;
81
82 /**
83 * creates an OSM api for a specific server URL
84 *
85 * @param serverUrl the server URL. Must not be null
86 * @exception IllegalArgumentException thrown, if serverUrl is null
87 */
88 protected SdsApi(String serverUrl) {
89 CheckParameterUtil.ensureParameterNotNull(serverUrl, "serverUrl");
90 this.serverUrl = serverUrl;
91 }
92
93 /**
94 * Returns the OSM protocol version we use to talk to the server.
95 * @return protocol version, or null if not yet negotiated.
96 */
97 public String getVersion() {
98 return version;
99 }
100
101
102 /**
103 * Returns the base URL for API requests, including the negotiated version number.
104 * @return base URL string
105 */
106 public String getBaseUrl() {
107 StringBuffer rv = new StringBuffer(serverUrl);
108 if (version != null) {
109 rv.append("/");
110 rv.append(version);
111 }
112 rv.append("/");
113 // this works around a ruby (or lighttpd) bug where two consecutive slashes in
114 // an URL will cause a "404 not found" response.
115 int p; while ((p = rv.indexOf("//", 6)) > -1) { rv.delete(p, p + 1); }
116 return rv.toString();
117 }
118
119 /**
120 * Creates an OSM primitive on the server. The OsmPrimitive object passed in
121 * is modified by giving it the server-assigned id.
122 *
123 * @param osm the primitive
124 * @throws SdsTransferException if something goes wrong
125
126 public void createPrimitive(IPrimitive osm, ProgressMonitor monitor) throws SdsTransferException {
127 String ret = "";
128 try {
129 ensureValidChangeset();
130 initialize(monitor);
131 ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/create", toXml(osm, true),monitor);
132 osm.setOsmId(Long.parseLong(ret.trim()), 1);
133 osm.setChangesetId(getChangeset().getId());
134 } catch(NumberFormatException e){
135 throw new SdsTransferException(tr("Unexpected format of ID replied by the server. Got ''{0}''.", ret));
136 }
137 }
138 */
139
140 /**
141 * Modifies an OSM primitive on the server.
142 *
143 * @param osm the primitive. Must not be null.
144 * @param monitor the progress monitor
145 * @throws SdsTransferException if something goes wrong
146
147 public void modifyPrimitive(IPrimitive osm, ProgressMonitor monitor) throws SdsTransferException {
148 String ret = null;
149 try {
150 ensureValidChangeset();
151 initialize(monitor);
152 // normal mode (0.6 and up) returns new object version.
153 ret = sendRequest("PUT", OsmPrimitiveType.from(osm).getAPIName()+"/" + osm.getId(), toXml(osm, true), monitor);
154 osm.setOsmId(osm.getId(), Integer.parseInt(ret.trim()));
155 osm.setChangesetId(getChangeset().getId());
156 osm.setVisible(true);
157 } catch(NumberFormatException e) {
158 throw new SdsTransferException(tr("Unexpected format of new version of modified primitive ''{0}''. Got ''{1}''.", osm.getId(), ret));
159 }
160 }
161 */
162
163 /**
164 * Deletes an OSM primitive on the server.
165 * @param osm the primitive
166 * @throws SdsTransferException if something goes wrong
167
168 public void deletePrimitive(IPrimitive osm, ProgressMonitor monitor) throws SdsTransferException {
169 ensureValidChangeset();
170 initialize(monitor);
171 // can't use a the individual DELETE method in the 0.6 API. Java doesn't allow
172 // submitting a DELETE request with content, the 0.6 API requires it, however. Falling back
173 // to diff upload.
174 //
175 uploadDiff(Collections.singleton(osm), monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
176 }
177 */
178
179 /**
180 * Uploads a list of changes in "diff" form to the server.
181 *
182 * @param list the list of changed OSM Primitives
183 * @param monitor the progress monitor
184 * @return
185 * @return list of processed primitives
186 * @throws SdsTransferException
187 * @throws SdsTransferException if something is wrong
188
189 public Collection<IPrimitive> uploadDiff(Collection<? extends IPrimitive> list, ProgressMonitor monitor) throws SdsTransferException {
190 try {
191 monitor.beginTask("", list.size() * 2);
192 if (changeset == null)
193 throw new SdsTransferException(tr("No changeset present for diff upload."));
194
195 initialize(monitor);
196
197 // prepare upload request
198 //
199 OsmChangeBuilder changeBuilder = new OsmChangeBuilder(changeset);
200 monitor.subTask(tr("Preparing upload request..."));
201 changeBuilder.start();
202 changeBuilder.append(list);
203 changeBuilder.finish();
204 String diffUploadRequest = changeBuilder.getDocument();
205
206 // Upload to the server
207 //
208 monitor.indeterminateSubTask(
209 trn("Uploading {0} object...", "Uploading {0} objects...", list.size(), list.size()));
210 String diffUploadResponse = sendRequest("POST", "changeset/" + changeset.getId() + "/upload", diffUploadRequest,monitor);
211
212 // Process the response from the server
213 //
214 DiffResultProcessor reader = new DiffResultProcessor(list);
215 reader.parse(diffUploadResponse, monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
216 return reader.postProcess(
217 getChangeset(),
218 monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)
219 );
220 } catch(SdsTransferException e) {
221 throw e;
222 } catch(OsmDataParsingException e) {
223 throw new SdsTransferException(e);
224 } finally {
225 monitor.finishTask();
226 }
227 }
228 */
229
230 public String requestShadowsFromSds(List<Long> nodes, List<Long> ways, List<Long> relations, ProgressMonitor pm) throws SdsTransferException {
231
232 StringBuilder request = new StringBuilder();
233 String delim = "";
234 String comma = "";
235
236 if (nodes != null && !nodes.isEmpty()) {
237 request.append(delim);
238 delim = "&";
239 comma = "";
240 request.append("nodes=");
241 for (long i : nodes) {
242 request.append(comma);
243 comma = ",";
244 request.append(i);
245 }
246 }
247 if (ways != null && !ways.isEmpty()) {
248 request.append(delim);
249 delim = "&";
250 comma = "";
251 request.append("ways=");
252 for (long i : ways) {
253 request.append(comma);
254 comma = ",";
255 request.append(i);
256 }
257 }
258 if (relations != null && !relations.isEmpty()) {
259 request.append(delim);
260 delim = "&";
261 comma = "";
262 request.append("relations=");
263 for (long i : relations) {
264 request.append(comma);
265 comma = ",";
266 request.append(i);
267 }
268 }
269
270 return sendRequest("POST", "collectshadows", request.toString(), pm ,true);
271
272 }
273
274 private void sleepAndListen(int retry, ProgressMonitor monitor) throws SdsTransferException {
275 System.out.print(tr("Waiting 10 seconds ... "));
276 for(int i=0; i < 10; i++) {
277 if (monitor != null) {
278 monitor.setCustomText(tr("Starting retry {0} of {1} in {2} seconds ...", getMaxRetries() - retry,getMaxRetries(), 10-i));
279 }
280 if (cancel)
281 throw new SdsTransferException();
282 try {
283 Thread.sleep(1000);
284 } catch (InterruptedException ex) {}
285 }
286 System.out.println(tr("OK - trying again."));
287 }
288
289 /**
290 * Replies the max. number of retries in case of 5XX errors on the server
291 *
292 * @return the max number of retries
293 */
294 protected int getMaxRetries() {
295 int ret = Main.pref.getInteger("osm-server.max-num-retries", DEFAULT_MAX_NUM_RETRIES);
296 return Math.max(ret,0);
297 }
298
299 private String sendRequest(String requestMethod, String urlSuffix, String requestBody, ProgressMonitor monitor) throws SdsTransferException {
300 return sendRequest(requestMethod, urlSuffix, requestBody, monitor, true, false);
301 }
302
303 private String sendRequest(String requestMethod, String urlSuffix, String requestBody, ProgressMonitor monitor, boolean doAuth) throws SdsTransferException {
304 return sendRequest(requestMethod, urlSuffix, requestBody, monitor, doAuth, false);
305 }
306
307 public boolean updateSds(String message, ProgressMonitor pm) {
308 try {
309 sendRequest("POST", "createshadows", message, pm);
310 } catch (SdsTransferException e) {
311 // TODO Auto-generated catch block
312 e.printStackTrace();
313 }
314 return true;
315 }
316
317 /**
318 * Generic method for sending requests to the OSM API.
319 *
320 * This method will automatically re-try any requests that are answered with a 5xx
321 * error code, or that resulted in a timeout exception from the TCP layer.
322 *
323 * @param requestMethod The http method used when talking with the server.
324 * @param urlSuffix The suffix to add at the server url, not including the version number,
325 * but including any object ids (e.g. "/way/1234/history").
326 * @param requestBody the body of the HTTP request, if any.
327 * @param monitor the progress monitor
328 * @param doAuthenticate set to true, if the request sent to the server shall include authentication
329 * credentials;
330 * @param fastFail true to request a short timeout
331 *
332 * @return the body of the HTTP response, if and only if the response code was "200 OK".
333 * @exception SdsTransferException if the HTTP return code was not 200 (and retries have
334 * been exhausted), or rewrapping a Java exception.
335 */
336 private String sendRequest(String requestMethod, String urlSuffix, String requestBody, ProgressMonitor monitor, boolean doAuthenticate, boolean fastFail) throws SdsTransferException {
337 StringBuffer responseBody = new StringBuffer();
338 int retries = getMaxRetries();
339
340 while(true) { // the retry loop
341 try {
342 URL url = new URL(new URL(getBaseUrl()), urlSuffix);
343 System.out.print(requestMethod + " " + url + "... ");
344 activeConnection = (HttpURLConnection)url.openConnection();
345 activeConnection.setConnectTimeout(fastFail ? 1000 : Main.pref.getInteger("socket.timeout.connect",15)*1000);
346 activeConnection.setRequestMethod(requestMethod);
347 if (doAuthenticate) {
348 addAuth(activeConnection);
349 }
350
351 if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) {
352 activeConnection.setDoOutput(true);
353 activeConnection.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
354 try (OutputStream out = activeConnection.getOutputStream()) {
355
356 // It seems that certain bits of the Ruby API are very unhappy upon
357 // receipt of a PUT/POST message without a Content-length header,
358 // even if the request has no payload.
359 // Since Java will not generate a Content-length header unless
360 // we use the output stream, we create an output stream for PUT/POST
361 // even if there is no payload.
362 if (requestBody != null) {
363 BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
364 bwr.write(requestBody);
365 bwr.flush();
366 }
367 }
368 }
369
370 activeConnection.connect();
371 System.out.println(activeConnection.getResponseMessage());
372 int retCode = activeConnection.getResponseCode();
373
374 if (retCode >= 500) {
375 if (retries-- > 0) {
376 sleepAndListen(retries, monitor);
377 System.out.println(tr("Starting retry {0} of {1}.", getMaxRetries() - retries,getMaxRetries()));
378 continue;
379 }
380 }
381
382 // populate return fields.
383 responseBody.setLength(0);
384
385 // If the API returned an error code like 403 forbidden, getInputStream
386 // will fail with an IOException.
387 InputStream i = null;
388 try {
389 i = activeConnection.getInputStream();
390 } catch (IOException ioe) {
391 i = activeConnection.getErrorStream();
392 }
393 if (i != null) {
394 // the input stream can be null if both the input and the error stream
395 // are null. Seems to be the case if the OSM server replies a 401
396 // Unauthorized, see #3887.
397 //
398 BufferedReader in = new BufferedReader(new InputStreamReader(i));
399 String s;
400 while((s = in.readLine()) != null) {
401 responseBody.append(s);
402 responseBody.append("\n");
403 }
404 }
405 String errorHeader = null;
406 // Look for a detailed error message from the server
407 if (activeConnection.getHeaderField("Error") != null) {
408 errorHeader = activeConnection.getHeaderField("Error");
409 System.err.println("Error header: " + errorHeader);
410 } else if (retCode != 200 && responseBody.length()>0) {
411 System.err.println("Error body: " + responseBody);
412 }
413 activeConnection.disconnect();
414
415 errorHeader = errorHeader == null? null : errorHeader.trim();
416 String errorBody = responseBody.length() == 0? null : responseBody.toString().trim();
417 switch(retCode) {
418 case HttpURLConnection.HTTP_OK:
419 return responseBody.toString();
420 case HttpURLConnection.HTTP_FORBIDDEN:
421 throw new SdsTransferException("FORBIDDEN");
422 default:
423 throw new SdsTransferException(errorHeader + errorBody);
424 }
425 } catch (UnknownHostException e) {
426 throw new SdsTransferException(e);
427 } catch (SocketTimeoutException e) {
428 if (retries-- > 0) {
429 continue;
430 }
431 throw new SdsTransferException(e);
432 } catch (ConnectException e) {
433 if (retries-- > 0) {
434 continue;
435 }
436 throw new SdsTransferException(e);
437 } catch(IOException e){
438 throw new SdsTransferException(e);
439 } catch(SdsTransferException e) {
440 throw e;
441 }
442 }
443 }
444
445 protected InputStream getInputStream(String urlStr, ProgressMonitor progressMonitor) throws SdsTransferException {
446 urlStr = getBaseUrl() + urlStr;
447 try {
448 URL url = null;
449 try {
450 url = new URL(urlStr.replace(" ", "%20"));
451 } catch(MalformedURLException e) {
452 throw new SdsTransferException(e);
453 }
454 try {
455 activeConnection = (HttpURLConnection)url.openConnection();
456 } catch(Exception e) {
457 throw new SdsTransferException(tr("Failed to open connection to API {0}.", url.toExternalForm()), e);
458 }
459 if (cancel) {
460 activeConnection.disconnect();
461 return null;
462 }
463
464 addAuth(activeConnection);
465
466 if (cancel)
467 throw new SdsTransferException();
468 if (Main.pref.getBoolean("osm-server.use-compression", true)) {
469 activeConnection.setRequestProperty("Accept-Encoding", "gzip, deflate");
470 }
471
472 activeConnection.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
473
474 try {
475 System.out.println("GET " + url);
476 activeConnection.connect();
477 } catch (Exception e) {
478 e.printStackTrace();
479 throw new SdsTransferException(tr("Could not connect to the OSM server. Please check your internet connection."), e);
480 }
481 try {
482 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED)
483 throw new OsmApiException(HttpURLConnection.HTTP_UNAUTHORIZED,null,null);
484
485 if (activeConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH)
486 throw new OsmTransferCanceledException(tr("Proxy Authentication Required"));
487
488 String encoding = activeConnection.getContentEncoding();
489 if (activeConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
490 String errorHeader = activeConnection.getHeaderField("Error");
491 StringBuilder errorBody = new StringBuilder();
492 try
493 {
494 InputStream i = FixEncoding(activeConnection.getErrorStream(), encoding);
495 if (i != null) {
496 BufferedReader in = new BufferedReader(new InputStreamReader(i));
497 String s;
498 while((s = in.readLine()) != null) {
499 errorBody.append(s);
500 errorBody.append("\n");
501 }
502 }
503 }
504 catch(Exception e) {
505 errorBody.append(tr("Reading error text failed."));
506 }
507
508 throw new OsmApiException(activeConnection.getResponseCode(), errorHeader, errorBody.toString());
509 }
510
511 return FixEncoding(new ProgressInputStream(activeConnection, progressMonitor), encoding);
512 } catch(Exception e) {
513 if (e instanceof SdsTransferException)
514 throw (SdsTransferException)e;
515 else
516 throw new SdsTransferException(e);
517
518 }
519 } finally {
520 progressMonitor.invalidate();
521 }
522 }
523
524 private InputStream FixEncoding(InputStream stream, String encoding) throws IOException
525 {
526 if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
527 stream = new GZIPInputStream(stream);
528 }
529 else if (encoding != null && encoding.equalsIgnoreCase("deflate")) {
530 stream = new InflaterInputStream(stream, new Inflater(true));
531 }
532 return stream;
533 }
534
535
536}
Note: See TracBrowser for help on using the repository browser.