source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java@ 13206

Last change on this file since 13206 was 12687, checked in by Don-vip, 7 years ago

see #15182 - move UploadStrategySpecification and required enums from gui.io to io

  • Property svn:eol-style set to native
File size: 16.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.CheckParameterUtil.ensureParameterNotNull;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.lang.reflect.InvocationTargetException;
10import java.util.HashSet;
11import java.util.Set;
12
13import javax.swing.JOptionPane;
14import javax.swing.SwingUtilities;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.APIDataSet;
18import org.openstreetmap.josm.data.osm.Changeset;
19import org.openstreetmap.josm.data.osm.ChangesetCache;
20import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
21import org.openstreetmap.josm.data.osm.IPrimitive;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.gui.HelpAwareOptionPane;
27import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
28import org.openstreetmap.josm.gui.Notification;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.gui.progress.ProgressMonitor;
31import org.openstreetmap.josm.gui.util.GuiHelper;
32import org.openstreetmap.josm.io.ChangesetClosedException;
33import org.openstreetmap.josm.io.MaxChangesetSizeExceededPolicy;
34import org.openstreetmap.josm.io.MessageNotifier;
35import org.openstreetmap.josm.io.OsmApi;
36import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
37import org.openstreetmap.josm.io.OsmServerWriter;
38import org.openstreetmap.josm.io.OsmTransferCanceledException;
39import org.openstreetmap.josm.io.OsmTransferException;
40import org.openstreetmap.josm.io.UploadStrategySpecification;
41import org.openstreetmap.josm.tools.ImageProvider;
42import org.openstreetmap.josm.tools.Logging;
43
44/**
45 * The task for uploading a collection of primitives.
46 * @since 2599
47 */
48public class UploadPrimitivesTask extends AbstractUploadTask {
49 private boolean uploadCanceled;
50 private Exception lastException;
51 private final APIDataSet toUpload;
52 private OsmServerWriter writer;
53 private final OsmDataLayer layer;
54 private Changeset changeset;
55 private final Set<IPrimitive> processedPrimitives;
56 private final UploadStrategySpecification strategy;
57
58 /**
59 * Creates the task
60 *
61 * @param strategy the upload strategy. Must not be null.
62 * @param layer the OSM data layer for which data is uploaded. Must not be null.
63 * @param toUpload the collection of primitives to upload. Set to the empty collection if null.
64 * @param changeset the changeset to use for uploading. Must not be null. changeset.getId()
65 * can be 0 in which case the upload task creates a new changeset
66 * @throws IllegalArgumentException if layer is null
67 * @throws IllegalArgumentException if toUpload is null
68 * @throws IllegalArgumentException if strategy is null
69 * @throws IllegalArgumentException if changeset is null
70 */
71 public UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, APIDataSet toUpload, Changeset changeset) {
72 super(tr("Uploading data for layer ''{0}''", layer.getName()), false /* don't ignore exceptions */);
73 ensureParameterNotNull(layer, "layer");
74 ensureParameterNotNull(strategy, "strategy");
75 ensureParameterNotNull(changeset, "changeset");
76 this.toUpload = toUpload;
77 this.layer = layer;
78 this.changeset = changeset;
79 this.strategy = strategy;
80 this.processedPrimitives = new HashSet<>();
81 }
82
83 protected MaxChangesetSizeExceededPolicy askMaxChangesetSizeExceedsPolicy() {
84 ButtonSpec[] specs = new ButtonSpec[] {
85 new ButtonSpec(
86 tr("Continue uploading"),
87 ImageProvider.get("upload"),
88 tr("Click to continue uploading to additional new changesets"),
89 null /* no specific help text */
90 ),
91 new ButtonSpec(
92 tr("Go back to Upload Dialog"),
93 ImageProvider.get("dialogs", "uploadproperties"),
94 tr("Click to return to the Upload Dialog"),
95 null /* no specific help text */
96 ),
97 new ButtonSpec(
98 tr("Abort"),
99 ImageProvider.get("cancel"),
100 tr("Click to abort uploading"),
101 null /* no specific help text */
102 )
103 };
104 int numObjectsToUploadLeft = toUpload.getSize() - processedPrimitives.size();
105 String msg1 = tr("The server reported that the current changeset was closed.<br>"
106 + "This is most likely because the changesets size exceeded the max. size<br>"
107 + "of {0} objects on the server ''{1}''.",
108 OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize(),
109 OsmApi.getOsmApi().getBaseUrl()
110 );
111 String msg2 = trn(
112 "There is {0} object left to upload.",
113 "There are {0} objects left to upload.",
114 numObjectsToUploadLeft,
115 numObjectsToUploadLeft
116 );
117 String msg3 = tr(
118 "Click ''<strong>{0}</strong>'' to continue uploading to additional new changesets.<br>"
119 + "Click ''<strong>{1}</strong>'' to return to the upload dialog.<br>"
120 + "Click ''<strong>{2}</strong>'' to abort uploading and return to map editing.<br>",
121 specs[0].text,
122 specs[1].text,
123 specs[2].text
124 );
125 String msg = "<html>" + msg1 + "<br><br>" + msg2 +"<br><br>" + msg3 + "</html>";
126 int ret = HelpAwareOptionPane.showOptionDialog(
127 Main.parent,
128 msg,
129 tr("Changeset is full"),
130 JOptionPane.WARNING_MESSAGE,
131 null, /* no special icon */
132 specs,
133 specs[0],
134 ht("/Action/Upload#ChangesetFull")
135 );
136 switch(ret) {
137 case 0: return MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS;
138 case 1: return MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG;
139 case 2:
140 case JOptionPane.CLOSED_OPTION:
141 default: return MaxChangesetSizeExceededPolicy.ABORT;
142 }
143 }
144
145 /**
146 * Opens a new changeset.
147 */
148 protected void openNewChangeset() {
149 // make sure the current changeset is removed from the upload dialog.
150 ChangesetCache.getInstance().update(changeset);
151 Changeset newChangeSet = new Changeset();
152 newChangeSet.setKeys(this.changeset.getKeys());
153 this.changeset = newChangeSet;
154 }
155
156 protected boolean recoverFromChangesetFullException() throws OsmTransferException {
157 if (toUpload.getSize() - processedPrimitives.size() == 0) {
158 strategy.setPolicy(MaxChangesetSizeExceededPolicy.ABORT);
159 return false;
160 }
161 if (strategy.getPolicy() == null || strategy.getPolicy().equals(MaxChangesetSizeExceededPolicy.ABORT)) {
162 strategy.setPolicy(askMaxChangesetSizeExceedsPolicy());
163 }
164 switch(strategy.getPolicy()) {
165 case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
166 // prepare the state of the task for a next iteration in uploading.
167 closeChangesetIfRequired();
168 openNewChangeset();
169 toUpload.removeProcessed(processedPrimitives);
170 return true;
171 case ABORT:
172 case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
173 default:
174 // don't continue - finish() will send the user back to map editing or upload dialog
175 return false;
176 }
177 }
178
179 /**
180 * Retries to recover the upload operation from an exception which was thrown because
181 * an uploaded primitive was already deleted on the server.
182 *
183 * @param e the exception throw by the API
184 * @param monitor a progress monitor
185 * @throws OsmTransferException if we can't recover from the exception
186 */
187 protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException {
188 if (!e.isKnownPrimitive()) throw e;
189 OsmPrimitive p = layer.data.getPrimitiveById(e.getPrimitiveId(), e.getPrimitiveType());
190 if (p == null) throw e;
191 if (p.isDeleted()) {
192 // we tried to delete an already deleted primitive.
193 final String msg;
194 final String displayName = p.getDisplayName(DefaultNameFormatter.getInstance());
195 if (p instanceof Node) {
196 msg = tr("Node ''{0}'' is already deleted. Skipping object in upload.", displayName);
197 } else if (p instanceof Way) {
198 msg = tr("Way ''{0}'' is already deleted. Skipping object in upload.", displayName);
199 } else if (p instanceof Relation) {
200 msg = tr("Relation ''{0}'' is already deleted. Skipping object in upload.", displayName);
201 } else {
202 msg = tr("Object ''{0}'' is already deleted. Skipping object in upload.", displayName);
203 }
204 monitor.appendLogMessage(msg);
205 Logging.warn(msg);
206 processedPrimitives.addAll(writer.getProcessedPrimitives());
207 processedPrimitives.add(p);
208 toUpload.removeProcessed(processedPrimitives);
209 return;
210 }
211 // exception was thrown because we tried to *update* an already deleted
212 // primitive. We can't resolve this automatically. Re-throw exception,
213 // a conflict is going to be created later.
214 throw e;
215 }
216
217 protected void cleanupAfterUpload() {
218 // we always clean up the data, even in case of errors. It's possible the data was
219 // partially uploaded. Better run on EDT.
220 Runnable r = () -> {
221 layer.cleanupAfterUpload(processedPrimitives);
222 layer.onPostUploadToServer();
223 ChangesetCache.getInstance().update(changeset);
224 };
225
226 try {
227 SwingUtilities.invokeAndWait(r);
228 } catch (InterruptedException e) {
229 Logging.trace(e);
230 lastException = e;
231 Thread.currentThread().interrupt();
232 } catch (InvocationTargetException e) {
233 Logging.trace(e);
234 lastException = new OsmTransferException(e.getCause());
235 }
236 }
237
238 @Override
239 protected void realRun() {
240 try {
241 MessageNotifier.stop();
242 uploadloop: while (true) {
243 try {
244 getProgressMonitor().subTask(
245 trn("Uploading {0} object...", "Uploading {0} objects...", toUpload.getSize(), toUpload.getSize()));
246 synchronized (this) {
247 writer = new OsmServerWriter();
248 }
249 writer.uploadOsm(strategy, toUpload.getPrimitives(), changeset, getProgressMonitor().createSubTaskMonitor(1, false));
250
251 // if we get here we've successfully uploaded the data. Exit the loop.
252 break;
253 } catch (OsmTransferCanceledException e) {
254 Logging.error(e);
255 uploadCanceled = true;
256 break uploadloop;
257 } catch (OsmApiPrimitiveGoneException e) {
258 // try to recover from 410 Gone
259 recoverFromGoneOnServer(e, getProgressMonitor());
260 } catch (ChangesetClosedException e) {
261 if (writer != null) {
262 processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out
263 }
264 switch(e.getSource()) {
265 case UPLOAD_DATA:
266 // Most likely the changeset is full. Try to recover and continue
267 // with a new changeset, but let the user decide first (see
268 // recoverFromChangesetFullException)
269 if (recoverFromChangesetFullException()) {
270 continue;
271 }
272 lastException = e;
273 break uploadloop;
274 case UNSPECIFIED:
275 case UPDATE_CHANGESET:
276 default:
277 // The changeset was closed when we tried to update it. Probably, our
278 // local list of open changesets got out of sync with the server state.
279 // The user will have to select another open changeset.
280 // Rethrow exception - this will be handled later.
281 changeset.setOpen(false);
282 throw e;
283 }
284 } finally {
285 if (writer != null) {
286 processedPrimitives.addAll(writer.getProcessedPrimitives());
287 }
288 synchronized (this) {
289 writer = null;
290 }
291 }
292 }
293 // if required close the changeset
294 closeChangesetIfRequired();
295 } catch (OsmTransferException e) {
296 if (uploadCanceled) {
297 Logging.info(tr("Ignoring caught exception because upload is canceled. Exception is: {0}", e.toString()));
298 } else {
299 lastException = e;
300 }
301 } finally {
302 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
303 MessageNotifier.start();
304 }
305 }
306 if (uploadCanceled && processedPrimitives.isEmpty()) return;
307 cleanupAfterUpload();
308 }
309
310 private void closeChangesetIfRequired() throws OsmTransferException {
311 if (strategy.isCloseChangesetAfterUpload() && changeset != null && !changeset.isNew() && changeset.isOpen()) {
312 OsmApi.getOsmApi().closeChangeset(changeset, progressMonitor.createSubTaskMonitor(0, false));
313 }
314 }
315
316 @Override protected void finish() {
317
318 // depending on the success of the upload operation and on the policy for
319 // multi changeset uploads this will sent the user back to the appropriate
320 // place in JOSM, either
321 // - to an error dialog
322 // - to the Upload Dialog
323 // - to map editing
324 GuiHelper.runInEDT(() -> {
325 // if the changeset is still open after this upload we want it to be selected on the next upload
326 ChangesetCache.getInstance().update(changeset);
327 if (changeset != null && changeset.isOpen()) {
328 UploadDialog.getUploadDialog().setSelectedChangesetForNextUpload(changeset);
329 }
330 if (uploadCanceled) return;
331 if (lastException == null) {
332 new Notification(
333 "<h3>" + tr("Upload successful!") + "</h3>")
334 .setIcon(ImageProvider.get("misc", "check_large"))
335 .show();
336 return;
337 }
338 if (lastException instanceof ChangesetClosedException) {
339 ChangesetClosedException e = (ChangesetClosedException) lastException;
340 if (e.getSource().equals(ChangesetClosedException.Source.UPDATE_CHANGESET)) {
341 handleFailedUpload(lastException);
342 return;
343 }
344 if (strategy.getPolicy() == null)
345 /* do nothing if unknown policy */
346 return;
347 if (e.getSource().equals(ChangesetClosedException.Source.UPLOAD_DATA)) {
348 switch(strategy.getPolicy()) {
349 case ABORT:
350 break; /* do nothing - we return to map editing */
351 case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
352 break; /* do nothing - we return to map editing */
353 case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
354 // return to the upload dialog
355 //
356 toUpload.removeProcessed(processedPrimitives);
357 UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
358 UploadDialog.getUploadDialog().setVisible(true);
359 break;
360 }
361 } else {
362 handleFailedUpload(lastException);
363 }
364 } else {
365 handleFailedUpload(lastException);
366 }
367 });
368 }
369
370 @Override protected void cancel() {
371 uploadCanceled = true;
372 synchronized (this) {
373 if (writer != null) {
374 writer.cancel();
375 }
376 }
377 }
378}
Note: See TracBrowser for help on using the repository browser.