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

Last change on this file was 19048, checked in by taylor.smock, 73 minutes ago

Dependency updates

ivy.xml

  • org.eclipse.parsson:parsson: 1.1.5 -> 1.1.6
  • org.apache.commons:commons-compress: 1.25.0 -> 1.26.1
    • Note: This deprecated some functions
  • ch.poole:OpeningHoursParser: 0.28.1 -> 0.28.2
  • org.jacoco:org.jacoco.ant: 0.8.11 -> 0.8.12
  • com.github.spotbugs:spotbugs-annotations: 4.8.3 -> 4.8.4
  • com.github.tomakehurst:wiremock: 2.35.0 -> 3.0.1
  • io.github.classgraph:classgraph: 4.8.165 -> 4.8.171
  • nl.jqno.equalsverifier:equalsverifier: 3.15.6 -> 3.16.1
  • org.awaitility:awaitility: 4.2.0 -> 4.2.1

tools/ivy.xml

  • com.puppycrawl.tools:checkstyle: 9.3 -> 10.15.0
  • com.github.spotbugs:spotbugs: 4.8.3 -> 4.8.4
  • com.google.errorprone: 2.(10.0|24.1) -> 2.26.1
  • net.sourceforge.pmd:pmd was not updated to 7.0.0 due to a significant number of false positives.

There were some additional changes to cleanup new warnings and remove some
additional Java 8 files. There are more warnings that need to be cleaned up.

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