source: josm/trunk/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java

Last change on this file was 19098, checked in by GerdP, 13 months ago

fix #23645: ChangesetCache: Is it useful as it is? Why isn't it always used?

  • use/update ChangesetCache when downloading changeset in HistoryBrowser
  • Property svn:eol-style set to native
File size: 10.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.history;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Component;
8import java.io.IOException;
9import java.net.HttpURLConnection;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.LinkedHashSet;
14import java.util.List;
15import java.util.Objects;
16import java.util.Set;
17
18import org.openstreetmap.josm.data.osm.Changeset;
19import org.openstreetmap.josm.data.osm.ChangesetCache;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.PrimitiveId;
22import org.openstreetmap.josm.data.osm.history.History;
23import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
24import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
25import org.openstreetmap.josm.gui.ExceptionDialogUtil;
26import org.openstreetmap.josm.gui.PleaseWaitRunnable;
27import org.openstreetmap.josm.gui.progress.ProgressMonitor;
28import org.openstreetmap.josm.io.ChangesetQuery;
29import org.openstreetmap.josm.io.OsmApiException;
30import org.openstreetmap.josm.io.OsmServerChangesetReader;
31import org.openstreetmap.josm.io.OsmServerHistoryReader;
32import org.openstreetmap.josm.io.OsmTransferException;
33import org.openstreetmap.josm.tools.CheckParameterUtil;
34import org.xml.sax.SAXException;
35
36/**
37 * Loads the object history of a collection of objects from the server.
38 *
39 * It provides a fluent API for configuration.
40 *
41 * Sample usage:
42 *
43 * <pre>
44 * HistoryLoadTask task = new HistoryLoadTask()
45 * .add(node)
46 * .add(way)
47 * .add(relation)
48 * .add(aHistoryItem);
49 *
50 * MainApplication.worker.execute(task);
51 * </pre>
52 */
53public class HistoryLoadTask extends PleaseWaitRunnable {
54
55 private boolean canceled;
56 private Exception lastException;
57 private final Set<PrimitiveId> toLoad = new LinkedHashSet<>();
58 private HistoryDataSet loadedData;
59 private OsmServerHistoryReader reader;
60 private boolean getChangesetData = true;
61 private boolean collectMissing;
62 private final Set<PrimitiveId> missingPrimitives = new LinkedHashSet<>();
63
64 /**
65 * Constructs a new {@code HistoryLoadTask}.
66 */
67 public HistoryLoadTask() {
68 super(tr("Load history"), true);
69 }
70
71 /**
72 * Constructs a new {@code HistoryLoadTask}.
73 *
74 * @param parent the component to be used as reference to find the
75 * parent for {@link org.openstreetmap.josm.gui.PleaseWaitDialog}.
76 * Must not be <code>null</code>.
77 * @throws NullPointerException if parent is <code>null</code>
78 */
79 public HistoryLoadTask(Component parent) {
80 super(Objects.requireNonNull(parent, "parent"), tr("Load history"), true);
81 }
82
83 /**
84 * Adds an object whose history is to be loaded.
85 *
86 * @param pid the primitive id. Must not be null. Id &gt; 0 required.
87 * @return this task
88 */
89 public HistoryLoadTask add(PrimitiveId pid) {
90 CheckParameterUtil.ensureThat(pid.getUniqueId() > 0, "id > 0");
91 toLoad.add(pid);
92 return this;
93 }
94
95 /**
96 * Adds an object to be loaded, the object is specified by a history item.
97 *
98 * @param primitive the history item
99 * @return this task
100 * @throws NullPointerException if primitive is null
101 */
102 public HistoryLoadTask add(HistoryOsmPrimitive primitive) {
103 return add(primitive.getPrimitiveId());
104 }
105
106 /**
107 * Adds an object to be loaded, the object is specified by an already loaded object history.
108 *
109 * @param history the history. Must not be null.
110 * @return this task
111 * @throws NullPointerException if history is null
112 */
113 public HistoryLoadTask add(History history) {
114 return add(history.getPrimitiveId());
115 }
116
117 /**
118 * Adds an object to be loaded, the object is specified by an OSM primitive.
119 *
120 * @param primitive the OSM primitive. Must not be null. primitive.getOsmId() &gt; 0 required.
121 * @return this task
122 * @throws NullPointerException if the primitive is null
123 * @throws IllegalArgumentException if primitive.getOsmId() &lt;= 0
124 */
125 public HistoryLoadTask add(OsmPrimitive primitive) {
126 CheckParameterUtil.ensureThat(primitive.getOsmId() > 0, "id > 0");
127 return add(primitive.getOsmPrimitiveId());
128 }
129
130 /**
131 * Adds a collection of objects to loaded, specified by a collection of OSM primitives.
132 *
133 * @param primitives the OSM primitives. Must not be <code>null</code>.
134 * <code>primitive.getId() &gt; 0</code> required.
135 * @return this task
136 * @throws NullPointerException if primitives is null
137 * @throws IllegalArgumentException if one of the ids in the collection &lt;= 0
138 * @since 16123
139 */
140 public HistoryLoadTask addPrimitiveIds(Collection<? extends PrimitiveId> primitives) {
141 primitives.forEach(this::add);
142 return this;
143 }
144
145 /**
146 * Adds a collection of objects to loaded, specified by a collection of OSM primitives.
147 *
148 * @param primitives the OSM primitives. Must not be <code>null</code>.
149 * <code>primitive.getId() &gt; 0</code> required.
150 * @return this task
151 * @throws NullPointerException if primitives is null
152 * @throws IllegalArgumentException if one of the ids in the collection &lt;= 0
153 * @since 16123
154 */
155 public HistoryLoadTask addOsmPrimitives(Collection<? extends OsmPrimitive> primitives) {
156 primitives.forEach(this::add);
157 return this;
158 }
159
160 @Override
161 protected void cancel() {
162 if (reader != null) {
163 reader.cancel();
164 }
165 canceled = true;
166 }
167
168 @Override
169 protected void finish() {
170 if (isCanceled())
171 return;
172 if (lastException != null) {
173 ExceptionDialogUtil.explainException(lastException);
174 return;
175 }
176 HistoryDataSet.getInstance().mergeInto(loadedData);
177 }
178
179 @Override
180 protected void realRun() throws SAXException, IOException, OsmTransferException {
181 loadedData = new HistoryDataSet();
182 int ticks = toLoad.size();
183 if (getChangesetData)
184 ticks *= 2;
185 try {
186 progressMonitor.setTicksCount(ticks);
187 for (PrimitiveId pid: toLoad) {
188 if (canceled) {
189 break;
190 }
191 loadHistory(pid);
192 }
193 } catch (OsmTransferException e) {
194 lastException = e;
195 }
196 }
197
198 private void loadHistory(PrimitiveId pid) throws OsmTransferException {
199 String msg = getLoadingMessage(pid);
200 progressMonitor.indeterminateSubTask(tr(msg, Long.toString(pid.getUniqueId())));
201 reader = null;
202 HistoryDataSet ds = null;
203 try {
204 reader = new OsmServerHistoryReader(pid.getType(), pid.getUniqueId());
205 if (getChangesetData) {
206 ds = loadHistory(reader, progressMonitor);
207 } else {
208 ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false));
209 }
210 } catch (OsmApiException e) {
211 if (canceled)
212 return;
213 if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND && collectMissing) {
214 missingPrimitives.add(pid);
215 } else {
216 throw e;
217 }
218 } catch (OsmTransferException e) {
219 if (canceled)
220 return;
221 throw e;
222 }
223 if (ds != null) {
224 loadedData.mergeInto(ds);
225 }
226 }
227
228 protected static HistoryDataSet loadHistory(OsmServerHistoryReader reader, ProgressMonitor progressMonitor) throws OsmTransferException {
229 HistoryDataSet ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false));
230 if (ds != null) {
231 // load corresponding changesets (mostly for changeset comment)
232 OsmServerChangesetReader changesetReader = new OsmServerChangesetReader();
233 List<Long> changesetIds = new ArrayList<>(ds.getChangesetIds());
234 Iterator<Long> iter = changesetIds.iterator();
235 while (iter.hasNext()) {
236 long id = iter.next();
237 Changeset cs = ChangesetCache.getInstance().get((int) id);
238 if (cs != null && !cs.isOpen()) {
239 ds.putChangeset(cs);
240 iter.remove();
241 }
242 }
243
244 // query changesets 100 by 100 (OSM API limit)
245 int n = ChangesetQuery.MAX_CHANGESETS_NUMBER;
246 for (int i = 0; i < changesetIds.size(); i += n) {
247 List<Changeset> downloadedCS = new ArrayList<>(changesetIds.size());
248 for (Changeset c : changesetReader.queryChangesets(
249 new ChangesetQuery().forChangesetIds(changesetIds.subList(i, Math.min(i + n, changesetIds.size()))),
250 progressMonitor.createSubTaskMonitor(1, false))) {
251 ds.putChangeset(c);
252 downloadedCS.add(c);
253 }
254 ChangesetCache.getInstance().update(downloadedCS);
255 }
256 }
257 return ds;
258 }
259
260 protected static String getLoadingMessage(PrimitiveId pid) {
261 switch (pid.getType()) {
262 case NODE:
263 return marktr("Loading history for node {0}");
264 case WAY:
265 return marktr("Loading history for way {0}");
266 case RELATION:
267 return marktr("Loading history for relation {0}");
268 default:
269 return "";
270 }
271 }
272
273 /**
274 * Determines if this task has ben canceled.
275 * @return {@code true} if this task has ben canceled
276 */
277 public boolean isCanceled() {
278 return canceled;
279 }
280
281 /**
282 * Returns the last exception that occurred during loading, if any.
283 * @return the last exception that occurred during loading, or {@code null}
284 */
285 public Exception getLastException() {
286 return lastException;
287 }
288
289 /**
290 * Determine if changeset information is needed. By default it is retrieved.
291 * @param b false means don't retrieve changeset data.
292 * @since 14763
293 */
294 public void setChangesetDataNeeded(boolean b) {
295 getChangesetData = b;
296 }
297
298 /**
299 * Determine if missing primitives should be collected. By default they are not collected
300 * and the first missing object terminates the task.
301 * @param b true means collect missing data and continue.
302 * @since 16205
303 */
304 public void setCollectMissing(boolean b) {
305 collectMissing = b;
306 }
307
308 /**
309 * replies the set of ids of all primitives for which a fetch request to the
310 * server was submitted but which are not available from the server (the server
311 * replied a return code of 404)
312 * @return the set of ids of missing primitives
313 * @since 16205
314 */
315 public Set<PrimitiveId> getMissingPrimitives() {
316 return missingPrimitives;
317 }
318
319}
Note: See TracBrowser for help on using the repository browser.