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

Last change on this file since 11457 was 10413, checked in by Don-vip, 8 years ago

fix #12983 - replace calls to Main.main.get[Active|Edit]Layer() by Main.getLayerManager().get[Active|Edit]Layer() - gsoc-core

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