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

Last change on this file since 1472 was 1415, checked in by stoecker, 15 years ago

applied patch #2185 by bruce89

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