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<?> taskFuture = executorService.submit(task) 034 * try { 035 * // wait for the task to complete 036 * taskFuture.get(); 037 * } catch (Exception e) { 038 * e.printStackTrace(); 039 * } 040 * </pre> 041 */ 042public class UploadLayerTask extends AbstractIOTask { 043 private OsmServerWriter writer; 044 private final OsmDataLayer layer; 045 private final ProgressMonitor monitor; 046 private final Changeset changeset; 047 private Collection<OsmPrimitive> toUpload; 048 private final Set<IPrimitive> processedPrimitives; 049 private final 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 if layer is null 059 * @throws IllegalArgumentException 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 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 Main.warn(tr("Object ''{0}'' is already deleted on the server. Skipping this object and retrying to upload.", 097 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 primitive. We can't resolve this automatically. 104 // Re-throw exception, a conflict is going to be created later. 105 throw e; 106 } 107 108 @Override 109 public void run() { 110 monitor.indeterminateSubTask(tr("Preparing objects to upload ...")); 111 APIDataSet ds = new APIDataSet(layer.data); 112 try { 113 ds.adjustRelationUploadOrder(); 114 } catch (CyclicUploadDependencyException e) { 115 setLastException(e); 116 return; 117 } 118 toUpload = ds.getPrimitives(); 119 if (toUpload.isEmpty()) 120 return; 121 writer = new OsmServerWriter(); 122 try { 123 while (true) { 124 try { 125 ProgressMonitor m = monitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false); 126 if (isCanceled()) return; 127 writer.uploadOsm(strategy, toUpload, changeset, m); 128 processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out 129 break; 130 } catch (OsmApiPrimitiveGoneException e) { 131 recoverFromGoneOnServer(e, monitor); 132 } 133 } 134 if (strategy.isCloseChangesetAfterUpload()) { 135 if (changeset != null && changeset.getId() > 0) { 136 OsmApi.getOsmApi().closeChangeset(changeset, monitor.createSubTaskMonitor(0, false)); 137 } 138 } 139 } catch (Exception sxe) { 140 if (isCanceled()) { 141 Main.info("Ignoring exception caught because upload is canceled. Exception is: " + sxe); 142 return; 143 } 144 setLastException(sxe); 145 } 146 147 if (isCanceled()) 148 return; 149 layer.cleanupAfterUpload(processedPrimitives); 150 layer.onPostUploadToServer(); 151 152 // don't process exceptions remembered with setLastException(). 153 // Caller is supposed to deal with them. 154 } 155 156 @Override 157 public void cancel() { 158 setCanceled(true); 159 if (writer != null) { 160 writer.cancel(); 161 } 162 } 163}