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

Last change on this file since 1180 was 1169, checked in by stoecker, 15 years ago

removed usage of tab stops

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