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