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 for (OsmPrimitive p: toUpload) { 075 if (OsmPrimitiveType.from(p).equals(type) && p.getId() == id) 076 return p; 077 } 078 return null; 079 } 080 081 /** 082 * Retries to recover the upload operation from an exception which was thrown because 083 * an uploaded primitive was already deleted on the server. 084 * 085 * @param e the exception throw by the API 086 * @throws OsmTransferException if we can't recover from the exception 087 */ 088 protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e) throws OsmTransferException { 089 if (!e.isKnownPrimitive()) throw e; 090 OsmPrimitive p = getPrimitive(e.getPrimitiveType(), e.getPrimitiveId()); 091 if (p == null) throw e; 092 if (p.isDeleted()) { 093 // we tried to delete an already deleted primitive. 094 Logging.warn(tr("Object ''{0}'' is already deleted on the server. Skipping this object and retrying to upload.", 095 p.getDisplayName(DefaultNameFormatter.getInstance()))); 096 processedPrimitives.addAll(writer.getProcessedPrimitives()); 097 processedPrimitives.add(p); 098 toUpload.removeAll(processedPrimitives); 099 return; 100 } 101 // exception was thrown because we tried to *update* an already deleted primitive. We can't resolve this automatically. 102 // Re-throw exception, a conflict is going to be created later. 103 throw e; 104 } 105 106 @Override 107 public void run() { 108 monitor.indeterminateSubTask(tr("Preparing objects to upload ...")); 109 APIDataSet ds = new APIDataSet(layer.getDataSet()); 110 try { 111 ds.adjustRelationUploadOrder(); 112 } catch (CyclicUploadDependencyException e) { 113 setLastException(e); 114 return; 115 } 116 toUpload = ds.getPrimitives(); 117 if (toUpload.isEmpty()) 118 return; 119 writer = new OsmServerWriter(); 120 try { 121 while (true) { 122 try { 123 ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false); 124 if (isCanceled()) return; 125 writer.uploadOsm(strategy, toUpload, changeset, m); 126 processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out 127 break; 128 } catch (OsmApiPrimitiveGoneException e) { 129 recoverFromGoneOnServer(e); 130 } 131 } 132 if (strategy.isCloseChangesetAfterUpload() && changeset != null && changeset.getId() > 0) { 133 OsmApi.getOsmApi().closeChangeset(changeset, monitor.createSubTaskMonitor(0, false)); 134 } 135 } catch (OsmTransferException sxe) { 136 if (isCanceled()) { 137 Logging.info("Ignoring exception caught because upload is canceled. Exception is: " + sxe); 138 return; 139 } 140 setLastException(sxe); 141 } 142 143 if (isCanceled()) 144 return; 145 layer.cleanupAfterUpload(processedPrimitives); 146 layer.onPostUploadToServer(); 147 148 // don't process exceptions remembered with setLastException(). 149 // Caller is supposed to deal with them. 150 } 151 152 @Override 153 public void cancel() { 154 setCanceled(true); 155 if (writer != null) { 156 writer.cancel(); 157 } 158 } 159}