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