Ignore:
Timestamp:
2009-12-09T21:24:32+01:00 (14 years ago)
Author:
Gubaer
Message:

comment to follow in a later commit
Have to break up a commit because of SSL problem in SVN (filed a ticket - #4093)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/UploadAction.java

    r2569 r2598  
    77import java.awt.event.ActionEvent;
    88import 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;
    159import java.util.LinkedList;
    1610import java.util.logging.Logger;
    17 import java.util.regex.Matcher;
    18 import java.util.regex.Pattern;
    1911
    2012import javax.swing.JOptionPane;
     
    2416import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook;
    2517import org.openstreetmap.josm.actions.upload.UploadHook;
    26 import org.openstreetmap.josm.actions.upload.UploadParameterHook;
    2718import org.openstreetmap.josm.data.APIDataSet;
    2819import 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;
    3720import org.openstreetmap.josm.gui.io.UploadDialog;
    38 import org.openstreetmap.josm.gui.io.UploadStrategySpecification;
     21import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
    3922import 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;
    5023import org.openstreetmap.josm.tools.Shortcut;
    51 import org.xml.sax.SAXException;
    5224
    5325/**
     
    8456         */
    8557        uploadHooks.add(new RelationUploadOrderHook());
    86 
    87         /**
    88          * Displays a screen where the actions that would be taken are displayed and
    89          * give the user the possibility to cancel the upload.
    90          */
    91         uploadHooks.add(new UploadParameterHook());
    9258    }
    9359
     
    168134        if (!checkPreUploadConditions(layer, apiData))
    169135            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
    170144        Main.worker.execute(
    171145                new UploadPrimitivesTask(
    172146                        UploadDialog.getUploadDialog().getUploadStrategySpecification(),
    173147                        layer,
    174                         apiData.getPrimitives(),
    175                         UploadDialog.getUploadDialog().getChangeset(),
    176                         UploadDialog.getUploadDialog().isDoCloseAfterUpload()
     148                        apiData,
     149                        UploadDialog.getUploadDialog().getChangeset()
    177150                )
    178151        );
     
    194167        uploadData(Main.map.mapView.getEditLayer(), apiData);
    195168    }
    196 
    197     /**
    198      * Synchronizes the local state of an {@see OsmPrimitive} with its state on the
    199      * server. The method uses an individual GET for the primitive.
    200      *
    201      * @param id the primitive ID
    202      */
    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 while
    221      * uploading
    222      *
    223      * @param primitiveType  the type of the primitive, either <code>node</code>, <code>way</code> or
    224      *    <code>relation</code>
    225      * @param id  the id of the primitive
    226      * @param serverVersion  the version of the primitive on the server
    227      * @param myVersion  the version of the primitive in the local dataset
    228      */
    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                         null
    242                 ),
    243                 new ButtonSpec(
    244                         tr("Synchronize entire dataset"),
    245                         ImageProvider.get("updatedata"),
    246                         null,
    247                         null
    248                 ),
    249                 new ButtonSpec(
    250                         tr("Cancel"),
    251                         ImageProvider.get("cancel"),
    252                         null,
    253                         null
    254                 )
    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].text
    266         );
    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't
    286      * 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                         null
    296                 ),
    297                 new ButtonSpec(
    298                         tr("Cancel"),
    299                         ImageProvider.get("cancel"),
    300                         null,
    301                         null
    302                 )
    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].text
    310         );
    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't
    328      * 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_MESSAGE
    342         );
    343     }
    344 
    345     /**
    346      * Handles the case where deleting a node failed because it is still in use in
    347      * 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].text
    370         );
    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 exception
    390      */
    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 exception
    421      */
    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 deleted
    436      * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we know what
    437      * {@see OsmPrimitive} is responsible for the error.
    438      *
    439      *  Reuses functionality of the {@see UpdateSelectionAction} to resolve
    440      *  conflicts due to mismatches in the deleted state.
    441      *
    442      * @param primitiveType the type of the primitive
    443      * @param id the id of the primitive
    444      *
    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 deleted
    454      * {@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 results
    456      * in a 409, not a 410.
    457      *
    458      * @param e the exception
    459      */
    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 upload
    470      *
    471      * @param e the exception
    472      */
    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 whether
    488             // and how to resolve it
    489             //
    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 on
    501             // 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 id
    514      *
    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 primitives
    558      *
    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 task
    573          * @param strategy the upload strategy
    574          * @param layer  the OSM data layer for which data is uploaded
    575          * @param toUpload the collection of primitives to upload
    576          * @param changeset the changeset to use for uploading
    577          * @param closeChangesetAfterUpload true, if the changeset is to be closed after uploading
    578          */
    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 because
    599          * an uploaded primitive was already deleted on the server.
    600          *
    601          * @param e the exception throw by the API
    602          * @param monitor a progress monitor
    603          * @throws OsmTransferException  thrown if we can't recover from the exception
    604          */
    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 deleted
    620             // 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 Gone
    638                         recoverFromGoneOnServer(e, getProgressMonitor());
    639                     }
    640                 }
    641                 // if required close the changeset
    642                 //
    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 was
    662             // partially uploaded
    663             //
    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     }
    684169}
Note: See TracChangeset for help on using the changeset viewer.