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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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