source: josm/trunk/src/org/openstreetmap/josm/io/OsmServerWriter.java@ 1381

Last change on this file since 1381 was 1338, checked in by stoecker, 15 years ago

close #761. modified patch by xeen

  • Property svn:eol-style set to native
File size: 26.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedOutputStream;
7import java.io.BufferedReader;
8import java.io.ByteArrayOutputStream;
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.StringWriter;
16import java.io.UnsupportedEncodingException;
17import java.lang.Math;
18import java.net.ConnectException;
19import java.net.HttpURLConnection;
20import java.net.URL;
21import java.net.UnknownHostException;
22import java.net.SocketTimeoutException;
23import java.util.Collection;
24import java.util.LinkedList;
25import javax.swing.JOptionPane;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.Node;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.Way;
32import org.openstreetmap.josm.data.osm.Changeset;
33import org.openstreetmap.josm.data.osm.visitor.CreateOsmChangeVisitor;
34import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
35import org.openstreetmap.josm.data.osm.visitor.Visitor;
36import org.xml.sax.SAXException;
37import org.openstreetmap.josm.io.XmlWriter.OsmWriterInterface;
38
39/**
40 * Class that uploads all changes to the osm server.
41 *
42 * This is done like this: - All objects with id = 0 are uploaded as new, except
43 * those in deleted, which are ignored - All objects in deleted list are
44 * deleted. - All remaining objects with modified flag set are updated.
45 *
46 * This class implements visitor and will perform the correct upload action on
47 * the visited element.
48 *
49 * @author imi
50 */
51public class OsmServerWriter extends OsmConnection implements Visitor {
52
53 /**
54 * This list contains all successfully processed objects. The caller of
55 * upload* has to check this after the call and update its dataset.
56 *
57 * If a server connection error occurs, this may contain fewer entries
58 * than where passed in the list to upload*.
59 */
60 public Collection<OsmPrimitive> processed;
61
62 /**
63 * Whether the operation should be aborted as soon as possible.
64 */
65 // use the inherited variable
66 // private boolean cancel = false;
67
68 /**
69 * Object describing current changeset
70 */
71 private Changeset changeset;
72
73 /**
74 * Send the dataset to the server. Ask the user first and does nothing if he
75 * does not want to send the data.
76 */
77 private static final int MSECS_PER_SECOND = 1000;
78 private static final int SECONDS_PER_MINUTE = 60;
79 private static final int MSECS_PER_MINUTE = MSECS_PER_SECOND * SECONDS_PER_MINUTE;
80
81 long uploadStartTime;
82
83 public String timeLeft(int progress, int list_size) {
84 long now = System.currentTimeMillis();
85 long elapsed = now - uploadStartTime;
86 if (elapsed == 0)
87 elapsed = 1;
88 float uploads_per_ms = (float)progress / elapsed;
89 float uploads_left = list_size - progress;
90 int ms_left = (int)(uploads_left / uploads_per_ms);
91 int minutes_left = ms_left / MSECS_PER_MINUTE;
92 int seconds_left = (ms_left / MSECS_PER_SECOND) % SECONDS_PER_MINUTE ;
93 String time_left_str = Integer.toString(minutes_left) + ":";
94 if (seconds_left < 10)
95 time_left_str += "0";
96 time_left_str += Integer.toString(seconds_left);
97 return time_left_str;
98 }
99
100 public void uploadOsm(Collection<OsmPrimitive> list) throws SAXException {
101 processed = new LinkedList<OsmPrimitive>();
102 initAuthentication();
103
104 Main.pleaseWaitDlg.progress.setMaximum(list.size());
105 Main.pleaseWaitDlg.progress.setValue(0);
106
107 // controls whether or not we open and close a changeset. API 0.6 requires changesets.
108 boolean useChangesets = Main.pref.get("osm-server.version", "0.5").equals("0.6");
109
110 // controls whether or not we try and uplaod the whole bunch in one go
111 boolean useDiffUploads = Main.pref.getBoolean("osm-server.atomic-upload",
112 Main.pref.get("osm-server.version", "0.5").equals("0.6"));
113
114 String comment = null;
115 while (useChangesets && comment == null) {
116 comment = JOptionPane.showInputDialog(Main.parent,
117 tr("Provide a brief comment for the changes you are uploading:"),
118 tr("Commit comment"), JOptionPane.QUESTION_MESSAGE);
119 if (comment == null)
120 return;
121 /* Don't let people just hit enter */
122 if( comment.trim().length() >= 3 )
123 break;
124 comment = null;
125 }
126 try {
127 if (useChangesets && !startChangeset(10, comment))
128 return;
129 }
130 catch (OsmTransferException ex) {
131 dealWithTransferException(ex);
132 return;
133 }
134
135 try {
136 if (useDiffUploads) {
137 uploadDiff(10, list);
138 } else {
139 NameVisitor v = new NameVisitor();
140 uploadStartTime = System.currentTimeMillis();
141 for (OsmPrimitive osm : list) {
142 if (cancel)
143 return;
144 osm.visit(v);
145 int progress = Main.pleaseWaitDlg.progress.getValue();
146 String time_left_str = timeLeft(progress, list.size());
147 Main.pleaseWaitDlg.currentAction.setText(
148 tr("{0}% ({1}/{2}), {3} left. Uploading {4}: {5} (id: {6})",
149 Math.round(100.0*progress/list.size()), progress,
150 list.size(), time_left_str, tr(v.className), v.name, osm.id));
151 osm.visit(this);
152 Main.pleaseWaitDlg.progress.setValue(progress+1);
153 }
154 }
155 if (useChangesets) stopChangeset(10);
156 } catch (RuntimeException e) {
157 try {
158 if (useChangesets) stopChangeset(10);
159 }
160 catch (OsmTransferException ex) {
161 dealWithTransferException(ex);
162 }
163 e.printStackTrace();
164 throw new SAXException(tr("An error occurred: {0}",e.getMessage()));
165 }
166 catch (OsmTransferException e) {
167 try {
168 if (useChangesets) stopChangeset(10);
169 }
170 catch (OsmTransferException ex) {
171 dealWithTransferException(ex);
172 }
173 dealWithTransferException(e);
174 }
175 }
176
177 /* FIXME: This code is terrible, please fix it!!!! */
178
179 /* Ok, this needs some explanation: The problem is that the code for
180 * retrying requests is intertwined with the code that generates the
181 * actual request. This means that for the retry code for the
182 * changeset stuff, it's basically a copy/cut/change slightly
183 * process. What actually needs to happen is that the retrying needs
184 * to be split from the creation of the requests and the retry loop
185 * handled in one place (preferably without recursion). While at you
186 * can fix the issue where hitting cancel doesn't do anything while
187 * retrying. - Mv0 Apr 2008
188 *
189 * Cancelling has an effect now, maybe it does not always catch on. Florian Heer, Aug 08
190 */
191 private boolean startChangeset(int retries, String comment) throws OsmTransferException {
192 Main.pleaseWaitDlg.currentAction.setText(tr("Opening changeset..."));
193 changeset = new Changeset();
194 changeset.put( "created_by", "JOSM" );
195 changeset.put( "comment", comment );
196 try {
197 if (cancel)
198 return false; // assume cancel
199 String version = Main.pref.get("osm-server.version", "0.6");
200 URL url = new URL(
201 Main.pref.get("osm-server.url") +
202 "/" + version +
203 "/" + "changeset" +
204 "/" + "create");
205 System.out.print("upload to: "+url+ "..." );
206 activeConnection = (HttpURLConnection)url.openConnection();
207 activeConnection.setConnectTimeout(15000);
208 activeConnection.setRequestMethod("PUT");
209 addAuth(activeConnection);
210
211 activeConnection.setDoOutput(true);
212 OutputStream out = activeConnection.getOutputStream();
213 OsmWriter.output(out, changeset);
214 out.close();
215
216 activeConnection.connect();
217 System.out.println("connected");
218
219 int retCode = activeConnection.getResponseCode();
220 if (retCode == 200)
221 changeset.id = readId(activeConnection.getInputStream());
222 System.out.println("got return: "+retCode+" with id "+changeset.id);
223 String retMsg = activeConnection.getResponseMessage();
224 activeConnection.disconnect();
225 if (retCode == 404) {
226 throw new OsmTransferException(tr("Server does not support changesets"));
227 }
228 if (retCode != 200 && retCode != 412) {
229
230 if (retries >= 0) {
231 retries--;
232 System.out.print("backing off for 10 seconds...");
233 Thread.sleep(10000);
234 System.out.println("retrying ("+retries+" left)");
235 return startChangeset(retries, comment);
236 }
237
238 // Look for a detailed error message from the server
239 retMsg += "\n" + readString(activeConnection.getInputStream());
240
241 // Report our error
242 ByteArrayOutputStream o = new ByteArrayOutputStream();
243 OsmWriter.output(o, changeset);
244 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
245 throw new OsmTransferException (retCode + " " + retMsg);
246 }
247 } catch (UnknownHostException e) {
248 throw new OsmTransferException(tr("Unknown host")+": "+e.getMessage(), e);
249 } catch(SocketTimeoutException e) {
250 System.out.println(" timed out, retries left: " + retries);
251 if (cancel)
252 return false; // assume cancel
253 if (retries-- > 0)
254 startChangeset(retries, comment);
255 else
256 throw new OsmTransferException (e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
257 }
258 catch (ConnectException e) {
259 System.out.println(" timed out, retries left: " + retries);
260 if (cancel)
261 return false; // assume cancel
262 if (retries-- > 0)
263 startChangeset(retries, comment);
264 else
265 throw new OsmTransferException (e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
266 }
267
268 catch (Exception e) {
269 if (cancel)
270 return false; // assume cancel
271 if (e instanceof OsmTransferException)
272 throw (OsmTransferException)e;
273 if (e instanceof RuntimeException)
274 throw (RuntimeException)e;
275 throw new RuntimeException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
276 }
277 return true;
278 }
279
280 private void uploadDiff(int retries, Collection<OsmPrimitive> list) throws OsmTransferException {
281
282 CreateOsmChangeVisitor duv = new CreateOsmChangeVisitor(changeset);
283
284 for (OsmPrimitive osm : list) {
285 int progress = Main.pleaseWaitDlg.progress.getValue();
286 Main.pleaseWaitDlg.currentAction.setText(tr("Preparing..."));
287 if (cancel)
288 return;
289 osm.visit(duv);
290 Main.pleaseWaitDlg.progress.setValue(progress+1);
291 }
292 System.out.println("the document:\n");
293 String diff = duv.getDocument();
294 System.out.println(diff);
295
296 Main.pleaseWaitDlg.currentAction.setText(tr("Uploading..."));
297 try {
298 if (cancel)
299 return; // assume cancel
300 String version = Main.pref.get("osm-server.version", "0.6");
301 URL url = new URL(
302 Main.pref.get("osm-server.url") +
303 "/" + version +
304 "/" + "changeset" +
305 "/" + changeset.id +
306 "/upload" );
307 System.out.print("upload to: "+url+ "..." );
308 activeConnection = (HttpURLConnection)url.openConnection();
309 activeConnection.setConnectTimeout(15000);
310 activeConnection.setRequestMethod("POST");
311 addAuth(activeConnection);
312
313 activeConnection.setDoOutput(true);
314 PrintWriter out;
315 try {
316 out = new PrintWriter(new OutputStreamWriter(activeConnection.getOutputStream(), "UTF-8"));
317 } catch (UnsupportedEncodingException e) {
318 throw new RuntimeException(e);
319 }
320 out.print(diff);
321 out.close();
322
323 activeConnection.connect();
324 System.out.println("connected");
325
326 int retCode = activeConnection.getResponseCode();
327 String retMsg = "";
328
329 if (retCode == 200) {
330 DiffResultReader.parseDiffResult(activeConnection.getInputStream(), list, processed, duv.getNewIdMap(), Main.pleaseWaitDlg);
331 } else if (retCode != 200 && retCode != 412) {
332 if (retries >= 0) {
333 retries--;
334 System.out.print("backing off for 10 seconds...");
335 Thread.sleep(10000);
336 System.out.println("retrying ("+retries+" left)");
337 stopChangeset(retries);
338 } else {
339 // Look for a detailed error message from the server
340 retMsg += "\n" + readString(activeConnection.getInputStream());
341
342 // Report our error
343 ByteArrayOutputStream o = new ByteArrayOutputStream();
344 OsmWriter.output(o, changeset);
345 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
346 throw new OsmTransferException(retCode+" "+retMsg);
347 }
348 }
349 } catch (UnknownHostException e) {
350 throw new OsmTransferException(tr("Unknown host")+": "+e.getMessage(), e);
351 } catch(SocketTimeoutException e) {
352 System.out.println(" timed out, retries left: " + retries);
353 if (cancel)
354 return; // assume cancel
355 if (retries-- > 0)
356 stopChangeset(retries);
357 else
358 throw new OsmTransferException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
359 } catch(ConnectException e) {
360 System.out.println(" timed out, retries left: " + retries);
361 if (cancel)
362 return; // assume cancel
363 if (retries-- > 0)
364 stopChangeset(retries);
365 else
366 throw new OsmTransferException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
367 } catch (Exception e) {
368 if (cancel)
369 return; // assume cancel
370 if (e instanceof OsmTransferException)
371 throw (OsmTransferException)e;
372 if (e instanceof RuntimeException)
373 throw (RuntimeException)e;
374 throw new RuntimeException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
375 }
376 }
377
378
379 private void stopChangeset(int retries) throws OsmTransferException {
380 Main.pleaseWaitDlg.currentAction.setText(tr("Closing changeset..."));
381 try {
382 if (cancel)
383 return; // assume cancel
384 String version = Main.pref.get("osm-server.version", "0.6");
385 URL url = new URL(
386 Main.pref.get("osm-server.url") +
387 "/" + version +
388 "/" + "changeset" +
389 "/" + changeset.id +
390 "/close" );
391 System.out.print("upload to: "+url+ "..." );
392 activeConnection = (HttpURLConnection)url.openConnection();
393 activeConnection.setConnectTimeout(15000);
394 activeConnection.setRequestMethod("PUT");
395 addAuth(activeConnection);
396
397 activeConnection.setDoOutput(true);
398 OutputStream out = activeConnection.getOutputStream();
399 OsmWriter.output(out, changeset);
400 out.close();
401
402 activeConnection.connect();
403 System.out.println("connected");
404
405 int retCode = activeConnection.getResponseCode();
406 System.out.println("got return: "+retCode);
407 String retMsg = activeConnection.getResponseMessage();
408 activeConnection.disconnect();
409 if (retCode == 404) {
410 System.out.println("Server does not support changesets, or the changeset could not be found, continuing");
411 return;
412 }
413 if (retCode != 200 && retCode != 412) {
414 if (retries >= 0) {
415 retries--;
416 System.out.print("backing off for 10 seconds...");
417 Thread.sleep(10000);
418 System.out.println("retrying ("+retries+" left)");
419 stopChangeset(retries);
420 } else {
421 // Look for a detailed error message from the server
422 retMsg += readString(activeConnection.getInputStream());
423
424 // Report our error
425 ByteArrayOutputStream o = new ByteArrayOutputStream();
426 OsmWriter.output(o, changeset);
427 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
428 throw new OsmTransferException(retCode+" "+retMsg);
429 }
430 }
431 } catch (UnknownHostException e) {
432 throw new OsmTransferException(tr("Unknown host")+": "+e.getMessage(), e);
433 } catch(SocketTimeoutException e) {
434 System.out.println(" timed out, retries left: " + retries);
435 if (cancel)
436 return; // assume cancel
437 if (retries-- > 0)
438 stopChangeset(retries);
439 else
440 throw new OsmTransferException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
441 } catch(ConnectException e) {
442 System.out.println(" timed out, retries left: " + retries);
443 if (cancel)
444 return; // assume cancel
445 if (retries-- > 0)
446 stopChangeset(retries);
447 else
448 throw new OsmTransferException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
449 } catch (Exception e) {
450 if (cancel)
451 return; // assume cancel
452 if (e instanceof OsmTransferException)
453 throw (OsmTransferException)e;
454 if (e instanceof RuntimeException)
455 throw (RuntimeException)e;
456 throw new RuntimeException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
457 }
458 }
459
460 /**
461 * Upload a single node.
462 */
463 public void visit(Node n) {
464 if (n.deleted) {
465 sendRequest("DELETE", "node", n, true);
466 } else {
467 sendRequest("PUT", "node", n, true);
468 }
469 processed.add(n);
470 }
471
472 /**
473 * Upload a whole way with the complete node id list.
474 */
475 public void visit(Way w) {
476 if (w.deleted) {
477 sendRequest("DELETE", "way", w, true);
478 } else {
479 sendRequest("PUT", "way", w, true);
480 }
481 processed.add(w);
482 }
483
484 /**
485 * Upload an relation with all members.
486 */
487 public void visit(Relation e) {
488 if (e.deleted) {
489 sendRequest("DELETE", "relation", e, true);
490 } else {
491 sendRequest("PUT", "relation", e, true);
492 }
493 processed.add(e);
494 }
495
496 /**
497 * Read a long from the input stream and return it.
498 */
499 private long readId(InputStream inputStream) throws IOException {
500 BufferedReader in = new BufferedReader(new InputStreamReader(
501 inputStream));
502 String s = in.readLine();
503 if (s == null)
504 return 0;
505 try {
506 return Long.parseLong(s);
507 } catch (NumberFormatException e) {
508 return 0;
509 }
510 }
511
512 /**
513 * Consume the input stream and return it as a string.
514 */
515 private String readString(InputStream inputStream) throws IOException {
516 BufferedReader in = new BufferedReader(new InputStreamReader(
517 inputStream));
518 StringBuffer sb = new StringBuffer();
519 String s;
520 while((s = in.readLine()) != null) {
521 sb.append(s);
522 sb.append("\n");
523 }
524 return sb.toString();
525 }
526
527 /**
528 * Send the request. The objects id will be replaced if it was 0 before
529 * (on add requests).
530 *
531 * @param requestMethod The http method used when talking with the server.
532 * @param urlSuffix The suffix to add at the server url.
533 * @param osm The primitive to encode to the server.
534 * @param body the body to be sent
535 */
536 private void sendRequestRetry(String requestMethod, String urlSuffix,
537 OsmPrimitive osm, OsmWriterInterface body, int retries) throws OsmTransferException {
538 try {
539 if (cancel)
540 return; // assume cancel
541 String version = Main.pref.get("osm-server.version", "0.5");
542 URL url = new URL(
543 new URL(Main.pref.get("osm-server.url") +
544 "/" + version + "/"),
545 urlSuffix +
546 "/" + (osm.id==0 ? "create" : osm.id),
547 new MyHttpHandler());
548 System.out.print("upload to: "+url+ "..." );
549 activeConnection = (HttpURLConnection)url.openConnection();
550 activeConnection.setConnectTimeout(15000);
551 activeConnection.setRequestMethod(requestMethod);
552 addAuth(activeConnection);
553 if (body != null) {
554 activeConnection.setDoOutput(true);
555 OutputStream out = activeConnection.getOutputStream();
556 OsmWriter.output(out, body);
557 out.close();
558 }
559 activeConnection.connect();
560 System.out.println("connected");
561
562 int retCode = activeConnection.getResponseCode();
563 /* When creating new, the returned value is the new id, otherwise it is the new version */
564 if (retCode == 200) {
565 if (osm.id == 0) {
566 osm.id = readId(activeConnection.getInputStream());
567 osm.version = 1;
568 } else {
569 int read_version = (int)readId(activeConnection.getInputStream());
570 if (read_version > 0)
571 osm.version = read_version;
572 }
573 } else {
574 System.out.println("got return: "+retCode+" with id "+osm.id);
575 }
576 activeConnection.disconnect();
577 if (retCode == 410 && requestMethod.equals("DELETE"))
578 return; // everything fine.. was already deleted.
579 else if (retCode != 200) {
580 if (retries >= 0 && retCode != 412) {
581 retries--;
582 System.out.print("backing off for 10 seconds...");
583 Thread.sleep(10000);
584 System.out.println("retrying ("+retries+" left)");
585 sendRequestRetry(requestMethod, urlSuffix, osm, body, retries);
586 } else {
587 String retMsg = activeConnection.getResponseMessage();
588 // Look for a detailed error message from the server
589 if (activeConnection.getHeaderField("Error") != null)
590 retMsg += "\n" + activeConnection.getHeaderField("Error");
591
592 // Report our error
593 ByteArrayOutputStream o = new ByteArrayOutputStream();
594 OsmWriter.output(o, body);
595 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
596 throw new OsmTransferException(retCode+" "+retMsg);
597 }
598 }
599 } catch (UnknownHostException e) {
600 throw new OsmTransferException(tr("Unknown host")+": "+e.getMessage(), e);
601 } catch(SocketTimeoutException e) {
602 System.out.println(" timed out, retries left: " + retries);
603 if (cancel)
604 return; // assume cancel
605 if (retries-- > 0)
606 sendRequestRetry(requestMethod, urlSuffix, osm, body, retries);
607 else
608 throw new OsmTransferException (e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
609 } catch(ConnectException e) {
610 System.out.println(" timed out, retries left: " + retries);
611 if (cancel)
612 return; // assume cancel
613 if (retries-- > 0)
614 sendRequestRetry(requestMethod, urlSuffix, osm, body, retries);
615 else
616 throw new OsmTransferException (e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
617 } catch (Exception e) {
618 if (cancel)
619 return; // assume cancel
620 if (e instanceof OsmTransferException)
621 throw (OsmTransferException)e;
622 if (e instanceof RuntimeException)
623 throw (RuntimeException)e;
624 throw new RuntimeException(e.getMessage()+ " " + e.getClass().getCanonicalName(), e);
625 }
626 }
627
628 private void sendRequest(String requestMethod, String urlSuffix,
629 OsmPrimitive osm, boolean addBody) {
630 XmlWriter.OsmWriterInterface body = null;
631 if (addBody) {
632 body = new OsmWriter.Single(osm, true, changeset);
633 }
634 try {
635 sendRequestRetry(requestMethod, urlSuffix, osm, body, 10);
636 }
637 catch (OsmTransferException e) {
638 dealWithTransferException (e);
639 }
640 }
641
642 private void dealWithTransferException (OsmTransferException e) {
643 Main.pleaseWaitDlg.currentAction.setText(tr("Transfer aborted due to error (will wait for 5 seconds):") + e.getMessage());
644 cancel = true;
645 try {
646 Thread.sleep(5000);
647 }
648 catch (InterruptedException ex) {}
649 }
650}
Note: See TracBrowser for help on using the repository browser.