Changeset 2598 in josm for trunk/src/org/openstreetmap/josm/actions/UploadAction.java
- Timestamp:
- 2009-12-09T21:24:32+01:00 (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/UploadAction.java
r2569 r2598 7 7 import java.awt.event.ActionEvent; 8 8 import java.awt.event.KeyEvent; 9 import java.io.IOException;10 import java.net.HttpURLConnection;11 import java.text.SimpleDateFormat;12 import java.util.Collection;13 import java.util.Date;14 import java.util.HashSet;15 9 import java.util.LinkedList; 16 10 import java.util.logging.Logger; 17 import java.util.regex.Matcher;18 import java.util.regex.Pattern;19 11 20 12 import javax.swing.JOptionPane; … … 24 16 import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook; 25 17 import org.openstreetmap.josm.actions.upload.UploadHook; 26 import org.openstreetmap.josm.actions.upload.UploadParameterHook;27 18 import org.openstreetmap.josm.data.APIDataSet; 28 19 import org.openstreetmap.josm.data.conflict.ConflictCollection; 29 import org.openstreetmap.josm.data.osm.Changeset;30 import org.openstreetmap.josm.data.osm.OsmPrimitive;31 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;32 import org.openstreetmap.josm.gui.DefaultNameFormatter;33 import org.openstreetmap.josm.gui.ExceptionDialogUtil;34 import org.openstreetmap.josm.gui.HelpAwareOptionPane;35 import org.openstreetmap.josm.gui.PleaseWaitRunnable;36 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;37 20 import org.openstreetmap.josm.gui.io.UploadDialog; 38 import org.openstreetmap.josm.gui.io.Upload StrategySpecification;21 import org.openstreetmap.josm.gui.io.UploadPrimitivesTask; 39 22 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 40 import org.openstreetmap.josm.gui.progress.ProgressMonitor;41 import org.openstreetmap.josm.io.ChangesetClosedException;42 import org.openstreetmap.josm.io.OsmApi;43 import org.openstreetmap.josm.io.OsmApiException;44 import org.openstreetmap.josm.io.OsmApiInitializationException;45 import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;46 import org.openstreetmap.josm.io.OsmServerWriter;47 import org.openstreetmap.josm.io.OsmTransferException;48 import org.openstreetmap.josm.tools.DateUtils;49 import org.openstreetmap.josm.tools.ImageProvider;50 23 import org.openstreetmap.josm.tools.Shortcut; 51 import org.xml.sax.SAXException;52 24 53 25 /** … … 84 56 */ 85 57 uploadHooks.add(new RelationUploadOrderHook()); 86 87 /**88 * Displays a screen where the actions that would be taken are displayed and89 * give the user the possibility to cancel the upload.90 */91 uploadHooks.add(new UploadParameterHook());92 58 } 93 59 … … 168 134 if (!checkPreUploadConditions(layer, apiData)) 169 135 return; 136 137 final UploadDialog dialog = UploadDialog.getUploadDialog(); 138 dialog.setUploadedPrimitives(apiData); 139 dialog.setVisible(true); 140 if (dialog.isCanceled()) 141 return; 142 dialog.rememberUserInput(); 143 170 144 Main.worker.execute( 171 145 new UploadPrimitivesTask( 172 146 UploadDialog.getUploadDialog().getUploadStrategySpecification(), 173 147 layer, 174 apiData.getPrimitives(), 175 UploadDialog.getUploadDialog().getChangeset(), 176 UploadDialog.getUploadDialog().isDoCloseAfterUpload() 148 apiData, 149 UploadDialog.getUploadDialog().getChangeset() 177 150 ) 178 151 ); … … 194 167 uploadData(Main.map.mapView.getEditLayer(), apiData); 195 168 } 196 197 /**198 * Synchronizes the local state of an {@see OsmPrimitive} with its state on the199 * server. The method uses an individual GET for the primitive.200 *201 * @param id the primitive ID202 */203 protected void synchronizePrimitive(final OsmPrimitiveType type, final long id) {204 Main.worker.execute(new UpdatePrimitiveTask(type, id));205 }206 207 /**208 * Synchronizes the local state of the dataset with the state on the server.209 *210 * Reuses the functionality of {@see UpdateDataAction}.211 *212 * @see UpdateDataAction#actionPerformed(ActionEvent)213 */214 protected void synchronizeDataSet() {215 UpdateDataAction act = new UpdateDataAction();216 act.actionPerformed(new ActionEvent(this,0,""));217 }218 219 /**220 * Handles the case that a conflict in a specific {@see OsmPrimitive} was detected while221 * uploading222 *223 * @param primitiveType the type of the primitive, either <code>node</code>, <code>way</code> or224 * <code>relation</code>225 * @param id the id of the primitive226 * @param serverVersion the version of the primitive on the server227 * @param myVersion the version of the primitive in the local dataset228 */229 protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primitiveType, final long id, String serverVersion, String myVersion) {230 String lbl = "";231 switch(primitiveType) {232 case NODE: lbl = tr("Synchronize node {0} only", id); break;233 case WAY: lbl = tr("Synchronize way {0} only", id); break;234 case RELATION: lbl = tr("Synchronize relation {0} only", id); break;235 }236 ButtonSpec[] spec = new ButtonSpec[] {237 new ButtonSpec(238 lbl,239 ImageProvider.get("updatedata"),240 null,241 null242 ),243 new ButtonSpec(244 tr("Synchronize entire dataset"),245 ImageProvider.get("updatedata"),246 null,247 null248 ),249 new ButtonSpec(250 tr("Cancel"),251 ImageProvider.get("cancel"),252 null,253 null254 )255 };256 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"257 + "of your nodes, ways, or relations.<br>"258 + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>"259 + "the server has version {2}, your version is {3}.<br>"260 + "<br>"261 + "Click <strong>{4}</strong> to synchronize the conflicting primitive only.<br>"262 + "Click <strong>{5}</strong> to synchronize the entire local dataset with the server.<br>"263 + "Click <strong>{6}</strong> to abort and continue editing.<br></html>",264 tr(primitiveType.getAPIName()), id, serverVersion, myVersion,265 spec[0].text, spec[1].text, spec[2].text266 );267 int ret = HelpAwareOptionPane.showOptionDialog(268 Main.parent,269 msg,270 tr("Conflicts detected"),271 JOptionPane.ERROR_MESSAGE,272 null,273 spec,274 spec[0],275 "/Concepts/Conflict"276 );277 switch(ret) {278 case 0: synchronizePrimitive(primitiveType, id); break;279 case 1: synchronizeDataSet(); break;280 default: return;281 }282 }283 284 /**285 * Handles the case that a conflict was detected while uploading where we don't286 * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)287 *288 */289 protected void handleUploadConflictForUnknownConflict() {290 ButtonSpec[] spec = new ButtonSpec[] {291 new ButtonSpec(292 tr("Synchronize entire dataset"),293 ImageProvider.get("updatedata"),294 null,295 null296 ),297 new ButtonSpec(298 tr("Cancel"),299 ImageProvider.get("cancel"),300 null,301 null302 )303 };304 String msg = tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>"305 + "of your nodes, ways, or relations.<br>"306 + "<br>"307 + "Click <strong>{0}</strong> to synchronize the entire local dataset with the server.<br>"308 + "Click <strong>{1}</strong> to abort and continue editing.<br></html>",309 spec[0].text, spec[1].text310 );311 int ret = HelpAwareOptionPane.showOptionDialog(312 Main.parent,313 msg,314 tr("Conflicts detected"),315 JOptionPane.ERROR_MESSAGE,316 null,317 spec,318 spec[0],319 "Concepts/Conflict"320 );321 if (ret == 0) {322 synchronizeDataSet();323 }324 }325 326 /**327 * Handles the case that a conflict was detected while uploading where we don't328 * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason)329 *330 */331 protected void handleUploadConflictForClosedChangeset(long changsetId, Date d) {332 String msg = tr("<html>Uploading <strong>failed</strong> because you''ve been using<br>"333 + "changeset {0} which was already closed at {1}.<br>"334 + "Please upload again with a new or an existing open changeset.</html>",335 changsetId, new SimpleDateFormat().format(d)336 );337 JOptionPane.showMessageDialog(338 Main.parent,339 msg,340 tr("Changeset closed"),341 JOptionPane.ERROR_MESSAGE342 );343 }344 345 /**346 * Handles the case where deleting a node failed because it is still in use in347 * a non-deleted way on the server.348 */349 protected void handleUploadConflictForNodeStillInUse(long nodeId, long wayId) {350 ButtonSpec[] options = new ButtonSpec[] {351 new ButtonSpec(352 tr("Prepare conflict resolution"),353 ImageProvider.get("ok"),354 tr("Click to download all parent ways for node {0}", nodeId),355 null /* no specific help context */356 ),357 new ButtonSpec(358 tr("Cancel"),359 ImageProvider.get("cancel"),360 tr("Click to cancel and to resume editing the map", nodeId),361 null /* no specific help context */362 )363 };364 String msg = tr("<html>Uploading <strong>failed</strong> because you tried "365 + "to delete node {0} which is still in use in way {1}.<br><br>"366 + "Click <strong>{2}</strong> to download all parent ways of node {0}.<br>"367 + "If necessary JOSM will create conflicts which you can resolve in the Conflict Resolution Dialog."368 + "</html>",369 nodeId, wayId, options[0].text370 );371 372 int ret = HelpAwareOptionPane.showOptionDialog(373 Main.parent,374 msg,375 tr("Node still in use"),376 JOptionPane.ERROR_MESSAGE,377 null,378 options,379 options[0],380 "/Action/Upload#NodeStillInUseInWay"381 );382 if (ret != 0) return;383 DownloadReferrersAction.downloadReferrers(Main.map.mapView.getEditLayer(), nodeId, OsmPrimitiveType.NODE);384 }385 386 /**387 * handles an upload conflict, i.e. an error indicated by a HTTP return code 409.388 *389 * @param e the exception390 */391 protected void handleUploadConflict(OsmApiException e) {392 String pattern = "Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)";393 Pattern p = Pattern.compile(pattern);394 Matcher m = p.matcher(e.getErrorHeader());395 if (m.matches()) {396 handleUploadConflictForKnownConflict(OsmPrimitiveType.from(m.group(3)), Long.parseLong(m.group(4)), m.group(2),m.group(1));397 return;398 }399 pattern ="The changeset (\\d+) was closed at (.*)";400 p = Pattern.compile(pattern);401 m = p.matcher(e.getErrorHeader());402 if (m.matches()) {403 handleUploadConflictForClosedChangeset(Long.parseLong(m.group(1)), DateUtils.fromString(m.group(2)));404 return;405 }406 pattern = "Node (\\d+) is still used by way (\\d+).";407 p = Pattern.compile(pattern);408 m = p.matcher(e.getErrorHeader());409 if (m.matches()) {410 handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));411 return;412 }413 logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));414 handleUploadConflictForUnknownConflict();415 }416 417 /**418 * handles an precondition failed conflict, i.e. an error indicated by a HTTP return code 412.419 *420 * @param e the exception421 */422 protected void handlePreconditionFailed(OsmApiException e) {423 String pattern = "Precondition failed: Node (\\d+) is still used by way (\\d+).";424 Pattern p = Pattern.compile(pattern);425 Matcher m = p.matcher(e.getErrorHeader());426 if (m.matches()) {427 handleUploadConflictForNodeStillInUse(Long.parseLong(m.group(1)), Long.parseLong(m.group(2)));428 return;429 }430 logger.warning(tr("Warning: error header \"{0}\" did not match with an expected pattern", e.getErrorHeader()));431 ExceptionDialogUtil.explainPreconditionFailed(e);432 }433 434 /**435 * Handles an error due to a delete request on an already deleted436 * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we know what437 * {@see OsmPrimitive} is responsible for the error.438 *439 * Reuses functionality of the {@see UpdateSelectionAction} to resolve440 * conflicts due to mismatches in the deleted state.441 *442 * @param primitiveType the type of the primitive443 * @param id the id of the primitive444 *445 * @see UpdateSelectionAction#handlePrimitiveGoneException(long)446 */447 protected void handleGoneForKnownPrimitive(OsmPrimitiveType primitiveType, long id) {448 UpdateSelectionAction act = new UpdateSelectionAction();449 act.handlePrimitiveGoneException(id,primitiveType);450 }451 452 /**453 * Handles an error which is caused by a delete request for an already deleted454 * {@see OsmPrimitive} on the server, i.e. a HTTP response code of 410.455 * Note that an <strong>update</strong> on an already deleted object results456 * in a 409, not a 410.457 *458 * @param e the exception459 */460 protected void handleGone(OsmApiPrimitiveGoneException e) {461 if (e.isKnownPrimitive()) {462 handleGoneForKnownPrimitive(e.getPrimitiveType(), e.getPrimitiveId());463 } else {464 ExceptionDialogUtil.explainGoneForUnknownPrimitive(e);465 }466 }467 468 /**469 * error handler for any exception thrown during upload470 *471 * @param e the exception472 */473 protected void handleFailedUpload(Exception e) {474 // API initialization failed. Notify the user and return.475 //476 if (e instanceof OsmApiInitializationException) {477 ExceptionDialogUtil.explainOsmApiInitializationException((OsmApiInitializationException)e);478 return;479 }480 481 if (e instanceof OsmApiPrimitiveGoneException) {482 handleGone((OsmApiPrimitiveGoneException)e);483 return;484 }485 if (e instanceof OsmApiException) {486 OsmApiException ex = (OsmApiException)e;487 // There was an upload conflict. Let the user decide whether488 // and how to resolve it489 //490 if(ex.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) {491 handleUploadConflict(ex);492 return;493 }494 // There was a precondition failed. Notify the user.495 //496 else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) {497 handlePreconditionFailed(ex);498 return;499 }500 // Tried to update or delete a primitive which never existed on501 // the server?502 //503 else if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {504 ExceptionDialogUtil.explainNotFound(ex);505 return;506 }507 }508 509 ExceptionDialogUtil.explainException(e);510 }511 512 /**513 * The asynchronous task to update a specific id514 *515 */516 class UpdatePrimitiveTask extends PleaseWaitRunnable {517 518 private boolean uploadCancelled = false;519 private boolean uploadFailed = false;520 private Exception lastException = null;521 private long id;522 private OsmPrimitiveType type;523 524 public UpdatePrimitiveTask(OsmPrimitiveType type, long id) {525 super(tr("Updating primitive"),false /* don't ignore exceptions */);526 this.id = id;527 this.type = type;528 }529 530 @Override protected void realRun() throws SAXException, IOException {531 try {532 UpdateSelectionAction act = new UpdateSelectionAction();533 act.updatePrimitive(type, id);534 } catch (Exception sxe) {535 if (uploadCancelled) {536 System.out.println("Ignoring exception caught because upload is canceled. Exception is: " + sxe.toString());537 return;538 }539 uploadFailed = true;540 lastException = sxe;541 }542 }543 544 @Override protected void finish() {545 if (uploadFailed) {546 handleFailedUpload(lastException);547 }548 }549 550 @Override protected void cancel() {551 OsmApi.getOsmApi().cancel();552 uploadCancelled = true;553 }554 }555 556 /**557 * The task for uploading a collection of primitives558 *559 */560 public class UploadPrimitivesTask extends PleaseWaitRunnable {561 private boolean uploadCancelled = false;562 private Exception lastException = null;563 private Collection <OsmPrimitive> toUpload;564 private OsmServerWriter writer;565 private OsmDataLayer layer;566 private Changeset changeset;567 private boolean closeChangesetAfterUpload;568 private HashSet<OsmPrimitive> processedPrimitives;569 private UploadStrategySpecification strategy;570 571 /**572 * Creates the task573 * @param strategy the upload strategy574 * @param layer the OSM data layer for which data is uploaded575 * @param toUpload the collection of primitives to upload576 * @param changeset the changeset to use for uploading577 * @param closeChangesetAfterUpload true, if the changeset is to be closed after uploading578 */579 private UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, Collection <OsmPrimitive> toUpload, Changeset changeset, boolean closeChangesetAfterUpload) {580 super(tr("Uploading data for layer ''{0}''", layer.getName()),false /* don't ignore exceptions */);581 this.toUpload = toUpload;582 this.layer = layer;583 this.changeset = changeset;584 this.strategy = strategy;585 this.closeChangesetAfterUpload = closeChangesetAfterUpload;586 this.processedPrimitives = new HashSet<OsmPrimitive>();587 }588 589 protected OsmPrimitive getPrimitive(OsmPrimitiveType type, long id) {590 for (OsmPrimitive p: toUpload) {591 if (OsmPrimitiveType.from(p).equals(type) && p.getId() == id)592 return p;593 }594 return null;595 }596 597 /**598 * Retries to recover the upload operation from an exception which was thrown because599 * an uploaded primitive was already deleted on the server.600 *601 * @param e the exception throw by the API602 * @param monitor a progress monitor603 * @throws OsmTransferException thrown if we can't recover from the exception604 */605 protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException{606 if (!e.isKnownPrimitive()) throw e;607 OsmPrimitive p = getPrimitive(e.getPrimitiveType(), e.getPrimitiveId());608 if (p == null) throw e;609 if (p.isDeleted()) {610 // we tried to delete an already deleted primitive.611 //612 System.out.println(tr("Warning: object ''{0}'' is already deleted on the server. Skipping this object and retrying to upload.", p.getDisplayName(DefaultNameFormatter.getInstance())));613 monitor.appendLogMessage(tr("Object ''{0}'' is already deleted. Skipping object in upload.",p.getDisplayName(DefaultNameFormatter.getInstance())));614 processedPrimitives.addAll(writer.getProcessedPrimitives());615 processedPrimitives.add(p);616 toUpload.removeAll(processedPrimitives);617 return;618 }619 // exception was thrown because we tried to *update* an already deleted620 // primitive. We can't resolve this automatically. Re-throw exception,621 // a conflict is going to be created later.622 throw e;623 }624 625 @Override protected void realRun() throws SAXException, IOException {626 writer = new OsmServerWriter();627 try {628 while(true) {629 try {630 getProgressMonitor().subTask(tr("Uploading {0} objects ...", toUpload.size()));631 writer.uploadOsm(strategy, toUpload, changeset, getProgressMonitor().createSubTaskMonitor(1, false));632 processedPrimitives.addAll(writer.getProcessedPrimitives());633 // if we get here we've successfully uploaded the data. Exit the loop.634 //635 break;636 } catch(OsmApiPrimitiveGoneException e) {637 // try to recover from the 410 Gone638 recoverFromGoneOnServer(e, getProgressMonitor());639 }640 }641 // if required close the changeset642 //643 if (closeChangesetAfterUpload) {644 if (changeset != null && changeset.getId() > 0) {645 OsmApi.getOsmApi().closeChangeset(changeset, progressMonitor.createSubTaskMonitor(0,false));646 }647 }648 } catch (Exception e) {649 if (uploadCancelled) {650 System.out.println(tr("Ignoring caught exception because upload is canceled. Exception is: {0}", e.toString()));651 return;652 }653 lastException = e;654 }655 }656 657 @Override protected void finish() {658 if (uploadCancelled)659 return;660 661 // we always clean up the data, even in case of errors. It's possible the data was662 // partially uploaded663 //664 layer.cleanupAfterUpload(processedPrimitives);665 layer.fireDataChange();666 if (lastException != null) {667 handleFailedUpload(lastException);668 }669 layer.onPostUploadToServer();670 if (lastException != null && lastException instanceof ChangesetClosedException) {671 UploadDialog.getUploadDialog().removeChangeset(changeset);672 } else {673 UploadDialog.getUploadDialog().setOrUpdateChangeset(changeset);674 }675 }676 677 @Override protected void cancel() {678 uploadCancelled = true;679 if (writer != null) {680 writer.cancel();681 }682 }683 }684 169 }
Note:
See TracChangeset
for help on using the changeset viewer.