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

Last change on this file since 2641 was 2641, checked in by Gubaer, 15 years ago

new: supports system defined proxies if JOSM is started with -Djava.net.useSystemProxies=true
fixed #1641: JOSM doesn't allow for setting HTTP proxy user/password distrinct from OSM server user/password
fixed #2865: SOCKS Proxy Support
fixed #4182: Proxy Authentication

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