Ignore:
Timestamp:
15.06.2009 20:22:46 (3 years ago)
Author:
Gubaer
Message:

fixed: bug in OsmApi.getOsmApi()
cleanup: exception handling in interfacing with OSM API
new: new action for updating individual elements with the their current state on the server (including new menu item in the file menu)
new: improved user feedback in case of conflicts
new: handles 410 Gone conflicts when uploading a changeset
new: undoable command for "purging" a primitive from the current dataset (necessary if the primitive is already deleted on the server and the user wants to remove it from its local dataset)
new: undoable command for "undeleting" an already deleted primitive on the server (kind of "cloning")
new: after a full upload, checks whether there are primitives in the local dataset which might be deleted on the server.
new: data structures for history data
new: history download support in io package

File:
1 edited

Legend:

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

    r1668 r1670  
    44import static org.openstreetmap.josm.tools.I18n.tr; 
    55 
    6 import java.awt.EventQueue; 
    76import java.awt.GridBagLayout; 
    87import java.awt.event.ActionEvent; 
     
    1312import java.util.LinkedList; 
    1413import java.util.List; 
    15 import java.util.concurrent.ExecutionException; 
    16 import java.util.concurrent.FutureTask; 
     14import java.util.logging.Logger; 
    1715import java.util.regex.Matcher; 
    1816import java.util.regex.Pattern; 
     
    2321import javax.swing.JPanel; 
    2422import javax.swing.JScrollPane; 
    25 import javax.swing.SwingUtilities; 
    2623 
    2724import org.openstreetmap.josm.Main; 
    2825import org.openstreetmap.josm.data.osm.OsmPrimitive; 
    29 import org.openstreetmap.josm.data.osm.visitor.CreateOsmChangeVisitor; 
    3026import org.openstreetmap.josm.gui.ExtendedDialog; 
    3127import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 
    3228import org.openstreetmap.josm.gui.PleaseWaitRunnable; 
    3329import org.openstreetmap.josm.gui.historycombobox.SuggestingJHistoryComboBox; 
    34 import org.openstreetmap.josm.io.DiffResultReader; 
    3530import org.openstreetmap.josm.io.OsmApi; 
    3631import org.openstreetmap.josm.io.OsmApiException; 
     
    4742 * An dialog is displayed asking the user to specify a rectangle to grab. 
    4843 * The url and account settings from the preferences are used. 
     44 *  
     45 * If the upload fails this action offers various options to resolve conflicts. 
    4946 * 
    5047 * @author imi 
    5148 */ 
    5249public class UploadAction extends JosmAction { 
     50    static private Logger logger = Logger.getLogger(UploadAction.class.getName()); 
    5351 
    5452    public static final String HISTORY_KEY = "upload.comment.history"; 
     
    215213                        return; 
    216214                    } 
    217                     System.out.println("got exception: " + sxe.toString()); 
    218215                    uploadFailed = true; 
    219216                    lastException = sxe; 
     
    230227                server.disconnectActiveConnection(); 
    231228                uploadCancelled = true; 
    232             }; 
    233  
    234         } 
     229            } 
     230        } 
     231 
    235232        Main.worker.execute(new UploadDiffTask()); 
    236233    } 
    237234 
    238     public void handleFailedUpload(Exception e) { 
     235    /** 
     236     * Synchronizes the local state of an {@see OsmPrimitive} with its state on the 
     237     * server. The method uses an individual GET for the primitive. 
     238     *  
     239     * @param id the primitive ID 
     240     */ 
     241    protected void synchronizePrimitive(final String id) { 
     242 
     243        /** 
     244         * The asynchronous task to update a a specific id 
     245         * 
     246         */ 
     247        class UpdatePrimitiveTask extends  PleaseWaitRunnable { 
     248 
     249            private boolean uploadCancelled = false; 
     250            private boolean uploadFailed = false; 
     251            private Exception lastException = null; 
     252 
     253            public UpdatePrimitiveTask() { 
     254                super(tr("Updating primitive"),false /* don't ignore exceptions */); 
     255            } 
     256 
     257            @Override protected void realRun() throws SAXException, IOException { 
     258                try { 
     259                    UpdateSelectionAction act = new UpdateSelectionAction(); 
     260                    act.updatePrimitive(Long.parseLong(id)); 
     261                } catch (Exception sxe) { 
     262                    if (uploadCancelled) { 
     263                        System.out.println("Ignoring exception caught because upload is cancelled. Exception is: " + sxe.toString()); 
     264                        return; 
     265                    } 
     266                    uploadFailed = true; 
     267                    lastException = sxe; 
     268                } 
     269            } 
     270 
     271            @Override protected void finish() { 
     272                if (uploadFailed) { 
     273                    handleFailedUpload(lastException); 
     274                } 
     275            } 
     276 
     277            @Override protected void cancel() { 
     278                OsmApi.getOsmApi().cancel(); 
     279                uploadCancelled = true; 
     280            } 
     281        } 
     282 
     283        Main.worker.execute(new UpdatePrimitiveTask()); 
     284    } 
     285 
     286    /** 
     287     * Synchronizes the local state of the dataset with the state on the server. 
     288     *  
     289     * Reuses the functionality of {@see UpdateDataAction}. 
     290     *  
     291     * @see UpdateDataAction#actionPerformed(ActionEvent) 
     292     */ 
     293    protected void synchronizeDataSet() { 
     294        UpdateDataAction act = new UpdateDataAction(); 
     295        act.actionPerformed(new ActionEvent(this,0,"")); 
     296    } 
     297 
     298    /** 
     299     * Handles the case that a conflict in a specific {@see OsmPrimitive} was detected while 
     300     * uploading 
     301     *  
     302     * @param primitiveType  the type of the primitive, either <code>node</code>, <code>way</code> or 
     303     *    <code>relation</code> 
     304     * @param id  the id of the primitive 
     305     * @param serverVersion  the version of the primitive on the server 
     306     * @param myVersion  the version of the primitive in the local dataset 
     307     */ 
     308    protected void handleUploadConflictForKnownConflict(String primitiveType, String id, String serverVersion, String myVersion) { 
     309        Object[] options = new Object[] { 
     310                tr("Synchronize {0} {1} only", tr(primitiveType), id), 
     311                tr("Synchronize entire dataset"), 
     312                tr("Cancel") 
     313        }; 
     314        Object defaultOption = options[0]; 
     315        String msg =  tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>" 
     316                + "of your nodes, ways, or relations.<br>" 
     317                + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>" 
     318                + "the server has version {2}, your version is {3}.<br>" 
     319                + "<br>" 
     320                + "Click <strong>{4}</strong> to synchronize the conflicting primitive only.<br>" 
     321                + "Click <strong>{5}</strong> to synchronize the entire local dataset with the server.<br>" 
     322                + "Click <strong>{6}</strong> to abort and continue editing.<br></html>", 
     323                tr(primitiveType), id, serverVersion, myVersion, 
     324                options[0], options[1], options[2] 
     325        ); 
     326        int optionsType = JOptionPane.YES_NO_CANCEL_OPTION; 
     327        int ret = JOptionPane.showOptionDialog( 
     328                null, 
     329                msg, 
     330                tr("Conflict detected"), 
     331                optionsType, 
     332                JOptionPane.ERROR_MESSAGE, 
     333                null, 
     334                options, 
     335                defaultOption 
     336        ); 
     337        switch(ret) { 
     338        case JOptionPane.CLOSED_OPTION: return; 
     339        case JOptionPane.CANCEL_OPTION: return; 
     340        case 0: synchronizePrimitive(id); break; 
     341        case 1: synchronizeDataSet(); break; 
     342        default: 
     343            // should not happen 
     344            throw new IllegalStateException(tr("unexpected return value. Got {0}", ret)); 
     345        } 
     346    } 
     347 
     348    /** 
     349     * Handles the case that a conflict was detected while uploading where we don't 
     350     * know what {@see OsmPrimitive} actually caused the conflict (for whatever reason) 
     351     *  
     352     */ 
     353    protected void handleUploadConflictForUnknownConflict() { 
     354        Object[] options = new Object[] { 
     355                tr("Synchronize entire dataset"), 
     356                tr("Cancel") 
     357        }; 
     358        Object defaultOption = options[0]; 
     359        String msg =  tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>" 
     360                + "of your nodes, ways, or relations.<br>" 
     361                + "<br>" 
     362                + "Click <strong>{0}</strong> to synchronize the entire local dataset with the server.<br>" 
     363                + "Click <strong>{1}</strong> to abort and continue editing.<br></html>", 
     364                options[0], options[1] 
     365        ); 
     366        int optionsType = JOptionPane.YES_NO_OPTION; 
     367        int ret = JOptionPane.showOptionDialog( 
     368                null, 
     369                msg, 
     370                tr("Conflict detected"), 
     371                optionsType, 
     372                JOptionPane.ERROR_MESSAGE, 
     373                null, 
     374                options, 
     375                defaultOption 
     376        ); 
     377        switch(ret) { 
     378        case JOptionPane.CLOSED_OPTION: return; 
     379        case 1: return; 
     380        case 0: synchronizeDataSet(); break; 
     381        default: 
     382            // should not happen 
     383            throw new IllegalStateException(tr("unexpected return value. Got {0}", ret)); 
     384        } 
     385    } 
     386 
     387    /** 
     388     * handles an upload conflict, i.e. an error indicated by a HTTP return code 409. 
     389     *  
     390     * @param e  the exception 
     391     */ 
     392    protected void handleUploadConflict(OsmApiException e) { 
     393        String pattern = "Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)"; 
     394        Pattern p = Pattern.compile(pattern); 
     395        Matcher m = p.matcher(e.getErrorHeader()); 
     396        if (m.matches()) { 
     397            handleUploadConflictForKnownConflict(m.group(3), m.group(4), m.group(2),m.group(1)); 
     398        } else { 
     399            logger.warning(tr("Warning: error header \"{0}\" did not match expected pattern \"{1}\"", e.getErrorHeader(),pattern)); 
     400            handleUploadConflictForUnknownConflict(); 
     401        } 
     402    } 
     403 
     404    /** 
     405     * Handles an upload error due to a violated precondition, i.e. a HTTP return code 412 
     406     *  
     407     * @param e the exception 
     408     */ 
     409    protected void handlePreconditionFailed(OsmApiException e) { 
     410        JOptionPane.showMessageDialog( 
     411                Main.parent, 
     412                tr("<html>Uploading to the server <strong>failed</strong> because your current<br>" 
     413                        +"dataset violates a precondition.<br>" 
     414                        +"The error message is:<br>" 
     415                        + "{0}" 
     416                        + "</html>", 
     417                        e.getMessage() 
     418                ), 
     419                tr("Precondition violation"), 
     420                JOptionPane.ERROR_MESSAGE 
     421        ); 
     422        e.printStackTrace(); 
     423    } 
     424 
     425 
     426    /** 
     427     * Handles an error due to a delete request on an already deleted 
     428     * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we know what 
     429     * {@see OsmPrimitive} is responsible for the error. 
     430     *  
     431     *  Reuses functionality of the {@see UpdateSelectionAction} to resolve 
     432     *  conflicts due to mismatches in the deleted state. 
     433     *  
     434     * @param primitiveType the type of the primitive 
     435     * @param id the id of the primitive 
     436     *  
     437     * @see UpdateSelectionAction#handlePrimitiveGoneException(long) 
     438     */ 
     439    protected void handleGoneForKnownPrimitive(String primitiveType, String id) { 
     440        UpdateSelectionAction act = new UpdateSelectionAction(); 
     441        act.handlePrimitiveGoneException(Long.parseLong(id)); 
     442    } 
     443 
     444    /** 
     445     * handles the case of an error due to a delete request on an already deleted 
     446     * {@see OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 
     447     * {@see OsmPrimitive} is causing the error. 
     448     *  
     449     * @param e the exception 
     450     */ 
     451    protected void handleGoneForUnknownPrimitive(OsmApiException e) { 
     452        String msg =  tr("<html>Uploading <strong>failed</strong> because a primitive you tried to<br>" 
     453                + "delete on the server is already deleted.<br>" 
     454                + "<br>" 
     455                + "The error message is:<br>" 
     456                + "{0}" 
     457                + "</html>", 
     458                e.getMessage() 
     459        ); 
     460        JOptionPane.showMessageDialog( 
     461                Main.parent, 
     462                msg, 
     463                tr("Primitive already deleted"), 
     464                JOptionPane.ERROR_MESSAGE 
     465        ); 
     466 
     467    } 
     468 
     469    /** 
     470     * Handles an error which is caused by a delete request for an already deleted 
     471     * {@see OsmPrimitive} on the server, i.e. a HTTP response code of 410. 
     472     * Note that an <strong>update</strong> on an already deleted object results 
     473     * in a 409, not a 410. 
     474     *  
     475     * @param e the exception 
     476     */ 
     477    protected void handleGone(OsmApiException e) { 
     478        String pattern = "The (\\S+) with the id (\\d+) has already been deleted"; 
     479        Pattern p = Pattern.compile(pattern); 
     480        Matcher m = p.matcher(e.getErrorHeader()); 
     481        if (m.matches()) { 
     482            handleGoneForKnownPrimitive(m.group(1), m.group(2)); 
     483        } else { 
     484            logger.warning(tr("Error header \"{0}\" doesn't match expected pattern \"{1}\"",e.getErrorHeader(), pattern)); 
     485            handleGoneForUnknownPrimitive(e); 
     486        } 
     487    } 
     488 
     489 
     490    /** 
     491     * error handler for any exception thrown during upload 
     492     *  
     493     * @param e the exception 
     494     */ 
     495    protected void handleFailedUpload(Exception e) { 
     496        // API initialization failed. Notify the user and return. 
     497        // 
    239498        if (e instanceof OsmApiInitializationException) { 
    240             handleOsmApiInitializationException(e); 
     499            handleOsmApiInitializationException((OsmApiInitializationException)e); 
    241500            return; 
    242501        } 
     502 
    243503        if (e instanceof OsmApiException) { 
    244504            OsmApiException ex = (OsmApiException)e; 
     505            // There was an upload conflict. Let the user decide whether 
     506            // and how to resolve it 
     507            // 
    245508            if(ex.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) { 
    246                 Pattern p = Pattern.compile("Version mismatch: Provided (\\d+), server had: (\\d+) of (\\S+) (\\d+)"); 
    247                 Matcher m = p.matcher(ex.getErrorHeader()); 
    248                 String msg; 
    249                 if (m.matches()) { 
    250                     msg =  tr("<html>Uploading <strong>failed</strong> because the server has a newer version of one<br>" 
    251                             + "of your nodes, ways or relations.<br>" 
    252                             + "The conflict is caused by the <strong>{0}</strong> with id <strong>{1}</strong>,<br>" 
    253                             + "the server has version {2}, your version is {3}.<br>" 
    254                             + "Please synchronize your local dataset using <br>" 
    255                             + "<strong>File-&gt;Update Data</strong>, resolve<br>" 
    256                             + "any conflicts and try to upload again.</html>", 
    257                             m.group(3),m.group(4), m.group(2), m.group(1) 
    258                     ); 
    259                 } else { 
    260                     msg =  tr("<html>Uploading failed because the server has a newer version of one<br>" 
    261                             + "of your nodes, ways or relations.<br>" 
    262                             + "Please synchronize your local dataset using <br>" 
    263                             + "<strong>File-&gt;Update Data</strong>, resolve<br>" 
    264                             + "any conflicts and try to upload again.</html>" 
    265                     ); 
    266                 } 
    267                 JOptionPane.showMessageDialog( 
    268                         null, 
    269                         msg, 
    270                         tr("Upload to OSM API failed"), 
    271                         JOptionPane.WARNING_MESSAGE 
    272                 ); 
     509                handleUploadConflict(ex); 
    273510                return; 
    274511            } 
    275         } 
     512            // There was a precondition failed. Notify the user. 
     513            // 
     514            else if (ex.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) { 
     515                handlePreconditionFailed(ex); 
     516                return; 
     517            } 
     518            // Tried to delete an already deleted primitive? Let the user 
     519            // decide whether and how to resolve this conflict. 
     520            // 
     521            else if (ex.getResponseCode() == HttpURLConnection.HTTP_GONE) { 
     522                handleGone(ex); 
     523                return; 
     524            } 
     525        } 
     526 
     527        // For any other exception just notify the user 
     528        // 
     529        String msg = e.getMessage().substring(0,Math.min(80, e.getMessage().length())); 
     530        if (msg.length() < e.getMessage().length()) { 
     531            msg += " ..."; 
     532        } 
     533        e.printStackTrace(); 
    276534        JOptionPane.showMessageDialog( 
    277535                null, 
    278                 e.getMessage(), 
     536                msg, 
    279537                tr("Upload to OSM API failed"), 
    280538                JOptionPane.ERROR_MESSAGE 
    281539        ); 
    282     } 
    283  
    284     protected void handleOsmApiInitializationException(Exception e) { 
     540 
     541    } 
     542 
     543    /** 
     544     * handles an exception caught during OSM API initialization 
     545     *  
     546     * @param e the exception 
     547     */ 
     548    protected void handleOsmApiInitializationException(OsmApiInitializationException e) { 
    285549        JOptionPane.showMessageDialog( 
    286                 null, 
     550                Main.parent, 
    287551                tr(   "Failed to initialize communication with the OSM server {0}.\n" 
    288552                        + "Check the server URL in your preferences and your internet connection.", 
Note: See TracChangeset for help on using the changeset viewer.