001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.Collection;
007import java.util.HashSet;
008import java.util.Set;
009
010import org.openstreetmap.josm.Main;
011import org.openstreetmap.josm.actions.upload.CyclicUploadDependencyException;
012import org.openstreetmap.josm.data.APIDataSet;
013import org.openstreetmap.josm.data.osm.Changeset;
014import org.openstreetmap.josm.data.osm.IPrimitive;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
017import org.openstreetmap.josm.gui.DefaultNameFormatter;
018import org.openstreetmap.josm.gui.layer.OsmDataLayer;
019import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
020import org.openstreetmap.josm.gui.progress.ProgressMonitor;
021import org.openstreetmap.josm.io.OsmApi;
022import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
023import org.openstreetmap.josm.io.OsmServerWriter;
024import org.openstreetmap.josm.io.OsmTransferException;
025import org.openstreetmap.josm.tools.CheckParameterUtil;
026
027/**
028 * UploadLayerTask uploads the data managed by an {@link OsmDataLayer} asynchronously.
029 *
030 * <pre>
031 *     ExecutorService executorService = ...
032 *     UploadLayerTask task = new UploadLayerTask(layer, monitor);
033 *     Future&lt;?&gt; taskFuture = executorServce.submit(task)
034 *     try {
035 *        // wait for the task to complete
036 *        taskFuture.get();
037 *     } catch(Exception e) {
038 *        e.printStackTracek();
039 *     }
040 * </pre>
041 */
042public class UploadLayerTask extends AbstractIOTask implements Runnable {
043    private OsmServerWriter writer;
044    private OsmDataLayer layer;
045    private ProgressMonitor monitor;
046    private Changeset changeset;
047    private Collection<OsmPrimitive> toUpload;
048    private Set<IPrimitive> processedPrimitives;
049    private UploadStrategySpecification strategy;
050
051    /**
052     * Creates the upload task
053     *
054     * @param strategy the upload strategy specification
055     * @param layer the layer. Must not be null.
056     * @param monitor  a progress monitor. If monitor is null, uses {@link NullProgressMonitor#INSTANCE}
057     * @param changeset the changeset to be used
058     * @throws IllegalArgumentException thrown, if layer is null
059     * @throws IllegalArgumentException thrown if strategy is null
060     */
061    public UploadLayerTask(UploadStrategySpecification strategy, OsmDataLayer layer, ProgressMonitor monitor, Changeset changeset) {
062        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
063        CheckParameterUtil.ensureParameterNotNull(strategy, "strategy");
064        if (monitor == null) {
065            monitor = NullProgressMonitor.INSTANCE;
066        }
067        this.layer = layer;
068        this.monitor = monitor;
069        this.changeset = changeset;
070        this.strategy = strategy;
071        processedPrimitives = new HashSet<>();
072    }
073
074    protected OsmPrimitive getPrimitive(OsmPrimitiveType type, long id) {
075        for (OsmPrimitive p: toUpload) {
076            if (OsmPrimitiveType.from(p).equals(type) && p.getId() == id)
077                return p;
078        }
079        return null;
080    }
081
082    /**
083     * Retries to recover the upload operation from an exception which was thrown because
084     * an uploaded primitive was already deleted on the server.
085     *
086     * @param e the exception throw by the API
087     * @param monitor a progress monitor
088     * @throws OsmTransferException  thrown if we can't recover from the exception
089     */
090    protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException{
091        if (!e.isKnownPrimitive()) throw e;
092        OsmPrimitive p = getPrimitive(e.getPrimitiveType(), e.getPrimitiveId());
093        if (p == null) throw e;
094        if (p.isDeleted()) {
095            // we tried to delete an already deleted primitive.
096            //
097            Main.warn(tr("Object ''{0}'' is already deleted on the server. Skipping this object and retrying to upload.", p.getDisplayName(DefaultNameFormatter.getInstance())));
098            processedPrimitives.addAll(writer.getProcessedPrimitives());
099            processedPrimitives.add(p);
100            toUpload.removeAll(processedPrimitives);
101            return;
102        }
103        // exception was thrown because we tried to *update* an already deleted
104        // primitive. We can't resolve this automatically. Re-throw exception,
105        // a conflict is going to be created later.
106        throw e;
107    }
108
109    @Override
110    public void run() {
111        monitor.indeterminateSubTask(tr("Preparing objects to upload ..."));
112        APIDataSet ds = new APIDataSet(layer.data);
113        try {
114            ds.adjustRelationUploadOrder();
115        } catch(CyclicUploadDependencyException e) {
116            setLastException(e);
117            return;
118        }
119        toUpload = ds.getPrimitives();
120        if (toUpload.isEmpty())
121            return;
122        writer = new OsmServerWriter();
123        try {
124            while(true) {
125                try {
126                    ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
127                    if (isCanceled()) return;
128                    writer.uploadOsm(strategy, toUpload, changeset, m);
129                    processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out
130                    break;
131                } catch(OsmApiPrimitiveGoneException e) {
132                    recoverFromGoneOnServer(e, monitor);
133                }
134            }
135            if (strategy.isCloseChangesetAfterUpload()) {
136                if (changeset != null && changeset.getId() > 0) {
137                    OsmApi.getOsmApi().closeChangeset(changeset, monitor.createSubTaskMonitor(0, false));
138                }
139            }
140        } catch (Exception sxe) {
141            if (isCanceled()) {
142                Main.info("Ignoring exception caught because upload is canceled. Exception is: " + sxe.toString());
143                return;
144            }
145            setLastException(sxe);
146        }
147
148        if (isCanceled())
149            return;
150        layer.cleanupAfterUpload(processedPrimitives);
151        layer.onPostUploadToServer();
152
153        // don't process exceptions remembered with setLastException().
154        // Caller is supposed to deal with them.
155    }
156
157    @Override
158    public void cancel() {
159        setCanceled(true);
160        if (writer != null) {
161            writer.cancel();
162        }
163    }
164}