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

Last change on this file since 16225 was 15586, checked in by Don-vip, 5 years ago

code cleanup

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