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

Last change on this file since 5266 was 5266, checked in by bastiK, 12 years ago

fixed majority of javadoc warnings by replacing "{@see" by "{@link"

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