source: josm/trunk/src/org/openstreetmap/josm/actions/UploadAction.java @ 11848

Last change on this file since 11848 was 11848, checked in by Don-vip, 2 years ago

fix #14613 - Special HTML characters not escaped in GUI error messages

  • Property svn:eol-style set to native
File size: 10.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.LinkedList;
10import java.util.List;
11
12import javax.swing.JOptionPane;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.actions.upload.ApiPreconditionCheckerHook;
16import org.openstreetmap.josm.actions.upload.DiscardTagsHook;
17import org.openstreetmap.josm.actions.upload.FixDataHook;
18import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook;
19import org.openstreetmap.josm.actions.upload.UploadHook;
20import org.openstreetmap.josm.actions.upload.ValidateUploadHook;
21import org.openstreetmap.josm.data.APIDataSet;
22import org.openstreetmap.josm.data.conflict.ConflictCollection;
23import org.openstreetmap.josm.gui.HelpAwareOptionPane;
24import org.openstreetmap.josm.gui.io.UploadDialog;
25import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
26import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
27import org.openstreetmap.josm.gui.layer.OsmDataLayer;
28import org.openstreetmap.josm.gui.util.GuiHelper;
29import org.openstreetmap.josm.tools.ImageProvider;
30import org.openstreetmap.josm.tools.Shortcut;
31import org.openstreetmap.josm.tools.Utils;
32
33/**
34 * Action that opens a connection to the osm server and uploads all changes.
35 *
36 * An dialog is displayed asking the user to specify a rectangle to grab.
37 * The url and account settings from the preferences are used.
38 *
39 * If the upload fails this action offers various options to resolve conflicts.
40 *
41 * @author imi
42 */
43public class UploadAction extends JosmAction {
44    /**
45     * The list of upload hooks. These hooks will be called one after the other
46     * when the user wants to upload data. Plugins can insert their own hooks here
47     * if they want to be able to veto an upload.
48     *
49     * Be default, the standard upload dialog is the only element in the list.
50     * Plugins should normally insert their code before that, so that the upload
51     * dialog is the last thing shown before upload really starts; on occasion
52     * however, a plugin might also want to insert something after that.
53     */
54    private static final List<UploadHook> uploadHooks = new LinkedList<>();
55    private static final List<UploadHook> lateUploadHooks = new LinkedList<>();
56
57    static {
58        /**
59         * Calls validator before upload.
60         */
61        uploadHooks.add(new ValidateUploadHook());
62
63        /**
64         * Fixes database errors
65         */
66        uploadHooks.add(new FixDataHook());
67
68        /**
69         * Checks server capabilities before upload.
70         */
71        uploadHooks.add(new ApiPreconditionCheckerHook());
72
73        /**
74         * Adjusts the upload order of new relations
75         */
76        uploadHooks.add(new RelationUploadOrderHook());
77
78        /**
79         * Removes discardable tags like created_by on modified objects
80         */
81        lateUploadHooks.add(new DiscardTagsHook());
82    }
83
84    /**
85     * Registers an upload hook. Adds the hook at the first position of the upload hooks.
86     *
87     * @param hook the upload hook. Ignored if null.
88     */
89    public static void registerUploadHook(UploadHook hook) {
90        registerUploadHook(hook, false);
91    }
92
93    /**
94     * Registers an upload hook. Adds the hook at the first position of the upload hooks.
95     *
96     * @param hook the upload hook. Ignored if null.
97     * @param late true, if the hook should be executed after the upload dialog
98     * has been confirmed. Late upload hooks should in general succeed and not
99     * abort the upload.
100     */
101    public static void registerUploadHook(UploadHook hook, boolean late) {
102        if (hook == null) return;
103        if (late) {
104            if (!lateUploadHooks.contains(hook)) {
105                lateUploadHooks.add(0, hook);
106            }
107        } else {
108            if (!uploadHooks.contains(hook)) {
109                uploadHooks.add(0, hook);
110            }
111        }
112    }
113
114    /**
115     * Unregisters an upload hook. Removes the hook from the list of upload hooks.
116     *
117     * @param hook the upload hook. Ignored if null.
118     */
119    public static void unregisterUploadHook(UploadHook hook) {
120        if (hook == null) return;
121        if (uploadHooks.contains(hook)) {
122            uploadHooks.remove(hook);
123        }
124        if (lateUploadHooks.contains(hook)) {
125            lateUploadHooks.remove(hook);
126        }
127    }
128
129    /**
130     * Constructs a new {@code UploadAction}.
131     */
132    public UploadAction() {
133        super(tr("Upload data"), "upload", tr("Upload all changes in the active data layer to the OSM server"),
134                Shortcut.registerShortcut("file:upload", tr("File: {0}", tr("Upload data")), KeyEvent.VK_UP, Shortcut.CTRL_SHIFT), true);
135        putValue("help", ht("/Action/Upload"));
136    }
137
138    /**
139     * Refreshes the enabled state
140     *
141     */
142    @Override
143    protected void updateEnabledState() {
144        OsmDataLayer editLayer = getLayerManager().getEditLayer();
145        setEnabled(editLayer != null && editLayer.isUploadable());
146    }
147
148    public static boolean checkPreUploadConditions(AbstractModifiableLayer layer) {
149        return checkPreUploadConditions(layer,
150                layer instanceof OsmDataLayer ? new APIDataSet(((OsmDataLayer) layer).data) : null);
151    }
152
153    protected static void alertUnresolvedConflicts(OsmDataLayer layer) {
154        HelpAwareOptionPane.showOptionDialog(
155                Main.parent,
156                tr("<html>The data to be uploaded participates in unresolved conflicts of layer ''{0}''.<br>"
157                        + "You have to resolve them first.</html>", Utils.escapeReservedCharactersHTML(layer.getName())
158                ),
159                tr("Warning"),
160                JOptionPane.WARNING_MESSAGE,
161                ht("/Action/Upload#PrimitivesParticipateInConflicts")
162        );
163    }
164
165    /**
166     * Warn user about discouraged upload, propose to cancel operation.
167     * @param layer incriminated layer
168     * @return true if the user wants to cancel, false if they want to continue
169     */
170    public static boolean warnUploadDiscouraged(AbstractModifiableLayer layer) {
171        return GuiHelper.warnUser(tr("Upload discouraged"),
172                "<html>" +
173                tr("You are about to upload data from the layer ''{0}''.<br /><br />"+
174                    "Sending data from this layer is <b>strongly discouraged</b>. If you continue,<br />"+
175                    "it may require you subsequently have to revert your changes, or force other contributors to.<br /><br />"+
176                    "Are you sure you want to continue?", Utils.escapeReservedCharactersHTML(layer.getName()))+
177                "</html>",
178                ImageProvider.get("upload"), tr("Ignore this hint and upload anyway"));
179    }
180
181    /**
182     * Check whether the preconditions are met to upload data in <code>apiData</code>.
183     * Makes sure upload is allowed, primitives in <code>apiData</code> don't participate in conflicts and
184     * runs the installed {@link UploadHook}s.
185     *
186     * @param layer the source layer of the data to be uploaded
187     * @param apiData the data to be uploaded
188     * @return true, if the preconditions are met; false, otherwise
189     */
190    public static boolean checkPreUploadConditions(AbstractModifiableLayer layer, APIDataSet apiData) {
191        if (layer.isUploadDiscouraged() && warnUploadDiscouraged(layer)) {
192            return false;
193        }
194        if (layer instanceof OsmDataLayer) {
195            OsmDataLayer osmLayer = (OsmDataLayer) layer;
196            ConflictCollection conflicts = osmLayer.getConflicts();
197            if (apiData.participatesInConflict(conflicts)) {
198                alertUnresolvedConflicts(osmLayer);
199                return false;
200            }
201        }
202        // Call all upload hooks in sequence.
203        // FIXME: this should become an asynchronous task
204        //
205        if (apiData != null) {
206            for (UploadHook hook : uploadHooks) {
207                if (!hook.checkUpload(apiData))
208                    return false;
209            }
210        }
211
212        return true;
213    }
214
215    /**
216     * Uploads data to the OSM API.
217     *
218     * @param layer the source layer for the data to upload
219     * @param apiData the primitives to be added, updated, or deleted
220     */
221    public void uploadData(final OsmDataLayer layer, APIDataSet apiData) {
222        if (apiData.isEmpty()) {
223            JOptionPane.showMessageDialog(
224                    Main.parent,
225                    tr("No changes to upload."),
226                    tr("Warning"),
227                    JOptionPane.INFORMATION_MESSAGE
228            );
229            return;
230        }
231        if (!checkPreUploadConditions(layer, apiData))
232            return;
233
234        final UploadDialog dialog = UploadDialog.getUploadDialog();
235        dialog.setChangesetTags(layer.data);
236        dialog.setUploadedPrimitives(apiData);
237        dialog.setVisible(true);
238        dialog.rememberUserInput();
239        if (dialog.isCanceled())
240            return;
241
242        for (UploadHook hook : lateUploadHooks) {
243            if (!hook.checkUpload(apiData))
244                return;
245        }
246
247        Main.worker.execute(
248                new UploadPrimitivesTask(
249                        UploadDialog.getUploadDialog().getUploadStrategySpecification(),
250                        layer,
251                        apiData,
252                        UploadDialog.getUploadDialog().getChangeset()
253                )
254        );
255    }
256
257    @Override
258    public void actionPerformed(ActionEvent e) {
259        if (!isEnabled())
260            return;
261        if (Main.map == null) {
262            JOptionPane.showMessageDialog(
263                    Main.parent,
264                    tr("Nothing to upload. Get some data first."),
265                    tr("Warning"),
266                    JOptionPane.WARNING_MESSAGE
267            );
268            return;
269        }
270        APIDataSet apiData = new APIDataSet(Main.getLayerManager().getEditDataSet());
271        uploadData(Main.getLayerManager().getEditLayer(), apiData);
272    }
273}
Note: See TracBrowser for help on using the repository browser.