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

Last change on this file since 2613 was 2613, checked in by Gubaer, 14 years ago

new: global in-memory cache for downloaded changesets
new: toggle dialog for changesets
new: downloading of changesets (currently without changeset content, will follow later)

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