source: josm/trunk/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java@ 2613

Last change on this file since 2613 was 2599, checked in by Gubaer, 14 years ago

fixed #4130: Chunked upload mode counter is wrong
fixed #4118: Upload dialog too complicated
fixed #4129: Hide the new "Upload data in one request/chunks/individually" behind an expanding "Upload method" box
fixed #2075: API 0.6: don't upload more than 50K edits at once
fixed #4044: Huge uploads never end [should be solved with chunked upload mode]
fixed #4110: Upload dialog spacing wrong
fixed #3386: Upload dialog has empty areas when the changeset doesn't include all of add/modify/delete operations
see #3369: bulk import helper [JOSM now supports multi changesets uploads]

See online help for more details.

Completes r2598

File size: 14.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.net.HttpURLConnection;
9import java.text.SimpleDateFormat;
10import java.util.Collections;
11import java.util.Date;
12import java.util.logging.Logger;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import javax.swing.JOptionPane;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.actions.DownloadReferrersAction;
20import org.openstreetmap.josm.actions.UpdateDataAction;
21import org.openstreetmap.josm.actions.UpdateSelectionAction;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
24import org.openstreetmap.josm.gui.ExceptionDialogUtil;
25import org.openstreetmap.josm.gui.HelpAwareOptionPane;
26import org.openstreetmap.josm.gui.PleaseWaitRunnable;
27import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
28import org.openstreetmap.josm.gui.layer.OsmDataLayer;
29import org.openstreetmap.josm.gui.progress.ProgressMonitor;
30import org.openstreetmap.josm.io.OsmApiException;
31import org.openstreetmap.josm.io.OsmApiInitializationException;
32import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
33import org.openstreetmap.josm.tools.DateUtils;
34import org.openstreetmap.josm.tools.ImageProvider;
35
36public abstract class AbstractUploadTask extends PleaseWaitRunnable {
37 private static final Logger logger = Logger.getLogger(AbstractUploadTask.class.getName());
38
39 public AbstractUploadTask(String title, boolean ignoreException) {
40 super(title, ignoreException);
41 }
42
43 public AbstractUploadTask(String title, ProgressMonitor progressMonitor, boolean ignoreException) {
44 super(title, progressMonitor, ignoreException);
45 }
46
47 public AbstractUploadTask(String title) {
48 super(title);
49 }
50
51 /**
52 * Synchronizes the local state of an {@see OsmPrimitive} with its state on the
53 * server. The method uses an individual GET for the primitive.
54 *
55 * @param id the primitive ID
56 */
57 protected void synchronizePrimitive(final OsmPrimitiveType type, final long id) {
58 // FIXME: should now about the layer this task is running for. might
59 // be different from the current edit layer
60 OsmDataLayer layer = Main.main.getEditLayer();
61 if (layer == null)
62 throw new IllegalStateException(tr("Failed to update primitive with id {0} because current edit layer is null", id));
63 OsmPrimitive p = layer.data.getPrimitiveById(id, type);
64 if (p == null)
65 throw new IllegalStateException(tr("Failed to update primitive with id {0} because current edit layer doesn't include such a primitive", id));
66 Main.worker.execute(new UpdatePrimitivesTask(layer, Collections.singleton(p)));
67 }
68
69 /**
70 * Synchronizes the local state of the dataset with the state on the server.
71 *
72 * Reuses the functionality of {@see UpdateDataAction}.
73 *
74 * @see UpdateDataAction#actionPerformed(ActionEvent)
75 */
76 protected void synchronizeDataSet() {
77 UpdateDataAction act = new UpdateDataAction();
78 act.actionPerformed(new ActionEvent(this,0,""));
79 }
80
81 /**
82 * Handles the case that a conflict in a specific {@see OsmPrimitive} was detected while
83 * uploading
84 *
85 * @param primitiveType the type of the primitive, either <code>node</code>, <code>way</code> or
86 * <code>relation</code>
87 * @param id the id of the primitive
88 * @param serverVersion the version of the primitive on the server
89 * @param myVersion the version of the primitive in the local dataset
90 */
91 protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primitiveType, final long id, String serverVersion, String myVersion) {
92 String lbl = "";
93 switch(primitiveType) {
94 case NODE: lbl = tr("Synchronize node {0} only", id); break;
95 case WAY: lbl = tr("Synchronize way {0} only", id); break;
96 case RELATION: lbl = tr("Synchronize relation {0} only", id); break;
97 }
98 ButtonSpec[] spec = new ButtonSpec[] {
99 new ButtonSpec(
100 lbl,
101 ImageProvider.get("updatedata"),
102 null,
103 null
104 ),
105 new ButtonSpec(
106 tr("Synchronize entire dataset"),
107 ImageProvider.get("updatedata"),
108 null,
109 null
110 ),
111 new ButtonSpec(
112 tr("Cancel"),
113 ImageProvider.get("cancel"),
114 null,
115 null
116 )
117 };
118 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
119 + "of your nodes, ways, or relations.<br>"
120 + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>"
121 + "the server has version {2}, your version is {3}.<br>"
122 + "<br>"
123 + "Click <strong>{4}</strong> to synchronize the conflicting primitive only.<br>"
124 + "Click <strong>{5}</strong> to synchronize the entire local dataset with the server.<br>"
125 + "Click <strong>{6}</strong> to abort and continue editing.<br></html>",
126 tr(primitiveType.getAPIName()), id, serverVersion, myVersion,
127 spec[0].text, spec[1].text, spec[2].text
128 );
129 int ret = HelpAwareOptionPane.showOptionDialog(
130 Main.parent,
131 msg,
132 tr("Conflicts detected"),
133 JOptionPane.ERROR_MESSAGE,
134 null,
135 spec,
136 spec[0],
137 "/Concepts/Conflict"
138 );
139 switch(ret) {
140 case 0: synchronizePrimitive(primitiveType, id); break;
141 case 1: synchronizeDataSet(); break;
142 default: return;
143 }
144 }
145
146 /**
147 * Handles the case that a conflict was detected while uploading where we don't
148 * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)
149 *
150 */
151 protected void handleUploadConflictForUnknownConflict() {
152 ButtonSpec[] spec = new ButtonSpec[] {
153 new ButtonSpec(
154 tr("Synchronize entire dataset"),
155 ImageProvider.get("updatedata"),
156 null,
157 null
158 ),
159 new ButtonSpec(
160 tr("Cancel"),
161 ImageProvider.get("cancel"),
162 null,
163 null
164 )
165 };
166 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"
167 + "of your nodes, ways, or relations.<br>"
168 + "<br>"
169 + "Click <strong>{0}</strong> to synchronize the entire local dataset with the server.<br>"
170 + "Click <strong>{1}</strong> to abort and continue editing.<br></html>",
171 spec[0].text, spec[1].text
172 );
173 int ret = HelpAwareOptionPane.showOptionDialog(
174 Main.parent,
175 msg,
176 tr("Conflicts detected"),
177 JOptionPane.ERROR_MESSAGE,
178 null,
179 spec,
180 spec[0],
181 ht("Concepts/Conflict")
182 );
183 if (ret == 0) {
184 synchronizeDataSet();
185 }
186 }
187
188 /**
189 * Handles the case that a conflict was detected while uploading where we don't
190 * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)
191 *
192 */
193 protected void handleUploadConflictForClosedChangeset(long changsetId, Date d) {
194 String msg = tr("<html>Uploading <strong>failed</strong> because you''ve been using<br>"
195 + "changeset {0} which was already closed at {1}.<br>"
196 + "Please upload again with a new or an existing open changeset.</html>",
197 changsetId, new SimpleDateFormat().format(d)
198 );
199 JOptionPane.showMessageDialog(
200 Main.parent,
201 msg,
202 tr("Changeset closed"),
203 JOptionPane.ERROR_MESSAGE
204 );
205 }
206
207 /**
208 * Handles the case where deleting a node failed because it is still in use in
209 * a non-deleted way on the server.
210 */
211 protected void handleUploadConflictForNodeStillInUse(long nodeId, long wayId) {
212 ButtonSpec[] options = new ButtonSpec[] {
213 new ButtonSpec(
214 tr("Prepare conflict resolution"),
215 ImageProvider.get("ok"),
216 tr("Click to download all parent ways for node {0}", nodeId),
217 null /* no specific help context */
218 ),
219 new ButtonSpec(
220 tr("Cancel"),
221 ImageProvider.get("cancel"),
222 tr("Click to cancel and to resume editing the map", nodeId),
223 null /* no specific help context */
224 )
225 };
226 String msg = tr("<html>Uploading <strong>failed</strong> because you tried "
227 + "to delete node {0} which is still in use in way {1}.<br><br>"
228 + "Click <strong>{2}</strong> to download all parent ways of node {0}.<br>"
229 + "If necessary JOSM will create conflicts which you can resolve in the Conflict Resolution Dialog."
230 + "</html>",
231 nodeId, wayId, options[0].text
232 );
233
234 int ret = HelpAwareOptionPane.showOptionDialog(
235 Main.parent,
236 msg,
237 tr("Node still in use"),
238 JOptionPane.ERROR_MESSAGE,
239 null,
240 options,
241 options[0],
242 "/Action/Upload#NodeStillInUseInWay"
243 );
244 if (ret != 0) return;
245 DownloadReferrersAction.downloadReferrers(Main.map.mapView.getEditLayer(), nodeId, OsmPrimitiveType.NODE);
246 }
247
248 /**
249 * handles an upload conflict, i.e. an error indicated by a HTTP return code 409.
250 *
251 * @param e the exception
252 */
253 protected void handleUploadConflict(OsmApiException e) {
254 String pattern = "Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)";
255 Pattern p = Pattern.compile(pattern);
256 Matcher m = p.matcher(e.getErrorHeader());
257 if (m.matches()) {
258 handleUploadConflictForKnownConflict(OsmPrimitiveType.from(m.group(3)), Long.parseLong(m.group(4)), m.group(2),m.group(1));
259 return;
260 }
261 pattern ="The changeset (\\d+) was closed at (.*)";
262 p = Pattern.compile(pattern);
263 m = p.matcher(e.getErrorHeader());
264 if (m.matches()) {
265 handleUploadConflictForClosedChangeset(Long.parseLong(m.group(1)), DateUtils.fromString(m.group(2)));
266 return;
267 }
268 pattern = "Node (\\d+) is still used by way (\\d+).";
269 p = Pattern.compile(pattern);
270 m = p.matcher(e.getErrorHeader());
271 if (m.matches()) {
272 handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));
273 return;
274 }
275 logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));
276 handleUploadConflictForUnknownConflict();
277 }
278
279 /**
280 * handles an precondition failed conflict, i.e. an error indicated by a HTTP return code 412.
281 *
282 * @param e the exception
283 */
284 protected void handlePreconditionFailed(OsmApiException e) {
285 String pattern = "Precondition failed: Node (\\d+) is still used by way (\\d+).";
286 Pattern p = Pattern.compile(pattern);
287 Matcher m = p.matcher(e.getErrorHeader());
288 if (m.matches()) {
289 handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));
290 return;
291 }
292 logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));
293 ExceptionDialogUtil.explainPreconditionFailed(e);
294 }
295
296 /**
297 * Handles an error which is caused by a delete request for an already deleted
298 * {@see OsmPrimitive} on the server, i.e. a HTTP response code of 410.
299 * Note that an <strong>update</strong> on an already deleted object results
300 * in a 409, not a 410.
301 *
302 * @param e the exception
303 */
304 protected void handleGone(OsmApiPrimitiveGoneException e) {
305 if (e.isKnownPrimitive()) {
306 new UpdateSelectionAction().handlePrimitiveGoneException(e.getPrimitiveId(),e.getPrimitiveType());
307 } else {
308 ExceptionDialogUtil.explainGoneForUnknownPrimitive(e);
309 }
310 }
311
312 /**
313 * error handler for any exception thrown during upload
314 *
315 * @param e the exception
316 */
317 protected void handleFailedUpload(Exception e) {
318 // API initialization failed. Notify the user and return.
319 //
320 if (e instanceof OsmApiInitializationException) {
321 ExceptionDialogUtil.explainOsmApiInitializationException((OsmApiInitializationException)e);
322 return;
323 }
324
325 if (e instanceof OsmApiPrimitiveGoneException) {
326 handleGone((OsmApiPrimitiveGoneException)e);
327 return;
328 }
329 if (e instanceof OsmApiException) {
330 OsmApiException ex = (OsmApiException)e;
331 // There was an upload conflict. Let the user decide whether
332 // and how to resolve it
333 //
334 if(ex.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {
335 handleUploadConflict(ex);
336 return;
337 }
338 // There was a precondition failed. Notify the user.
339 //
340 else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) {
341 handlePreconditionFailed(ex);
342 return;
343 }
344 // Tried to update or delete a primitive which never existed on
345 // the server?
346 //
347 else if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
348 ExceptionDialogUtil.explainNotFound(ex);
349 return;
350 }
351 }
352
353 ExceptionDialogUtil.explainException(e);
354 }
355}
Note: See TracBrowser for help on using the repository browser.