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

Last change on this file since 1023 was 977, checked in by stoecker, 16 years ago

handle 412 as upload error. Closes #1316

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