- Timestamp:
- 2012-07-31T23:27:44+02:00 (12 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
r5266 r5387 7 7 import java.io.InputStream; 8 8 import java.net.HttpURLConnection; 9 import java.util.ArrayList; 9 10 import java.util.Collection; 10 11 import java.util.HashSet; 11 12 import java.util.Iterator; 12 13 import java.util.LinkedHashSet; 14 import java.util.List; 13 15 import java.util.NoSuchElementException; 14 16 import java.util.Set; 15 17 import java.util.concurrent.Callable; 18 import java.util.concurrent.CompletionService; 19 import java.util.concurrent.ExecutionException; 20 import java.util.concurrent.Executor; 21 import java.util.concurrent.ExecutorCompletionService; 22 import java.util.concurrent.Executors; 23 import java.util.concurrent.Future; 24 25 import org.openstreetmap.josm.Main; 16 26 import org.openstreetmap.josm.data.osm.DataSet; 17 27 import org.openstreetmap.josm.data.osm.DataSetMerger; … … 45 55 * } 46 56 * </pre> 47 *48 *49 57 */ 50 58 public class MultiFetchServerObjectReader extends OsmServerReader{ … … 54 62 * which should be safe according to the 55 63 * <a href="http://www.boutell.com/newfaq/misc/urllength.html">WWW FAQ</a>. 56 *57 64 */ 58 65 static private int MAX_IDS_PER_REQUEST = 200; … … 65 72 66 73 /** 67 * constructor 68 * 74 * Constructs a {@code MultiFetchServerObjectReader}. 69 75 */ 70 76 public MultiFetchServerObjectReader() { … … 101 107 * 102 108 * @param ds the dataset (must not be null) 103 * @param id the id104 * @ exception IllegalArgumentException thrown, if ds is null105 * @ exception NoSuchElementException thrown, if ds doesn't include an {@link OsmPrimitive} with106 * 109 * @param id the primitive id 110 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 111 * @throws IllegalArgumentException if ds is null 112 * @throws NoSuchElementException if ds does not include an {@link OsmPrimitive} with id=<code>id</code> 107 113 */ 108 114 protected void remember(DataSet ds, long id, OsmPrimitiveType type) throws IllegalArgumentException, NoSuchElementException{ … … 116 122 } 117 123 124 /** 125 * appends a {@link OsmPrimitive} id to the list of ids which will be fetched from the server. 126 * 127 * @param ds the {@link DataSet} to which the primitive belongs 128 * @param id the primitive id 129 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 130 * @return this 131 */ 118 132 public MultiFetchServerObjectReader append(DataSet ds, long id, OsmPrimitiveType type) { 133 OsmPrimitive p = ds.getPrimitiveById(id,type); 119 134 switch(type) { 120 135 case NODE: 121 Node n = (Node)ds.getPrimitiveById(id,type); 122 appendNode(n); 123 break; 136 return appendNode((Node)p); 124 137 case WAY: 125 Way w= (Way)ds.getPrimitiveById(id,type); 126 appendWay(w); 127 break; 138 return appendWay((Way)p); 128 139 case RELATION: 129 Relation r = (Relation)ds.getPrimitiveById(id,type); 130 appendRelation(r); 131 break; 140 return appendRelation((Relation)p); 132 141 } 133 142 return this; … … 135 144 136 145 /** 137 * appends a {@link Node} sid to the list of ids which will be fetched from the server.146 * appends a {@link Node} id to the list of ids which will be fetched from the server. 138 147 * 139 148 * @param node the node (ignored, if null) 140 149 * @return this 141 *142 150 */ 143 151 public MultiFetchServerObjectReader appendNode(Node node) { … … 148 156 149 157 /** 150 * appends a {@link Way} sid and the list of ids of nodes the way refers to the list of ids which will be fetched from the server.158 * appends a {@link Way} id and the list of ids of nodes the way refers to the list of ids which will be fetched from the server. 151 159 * 152 160 * @param way the way (ignored, if null) 153 161 * @return this 154 *155 162 */ 156 163 public MultiFetchServerObjectReader appendWay(Way way) { … … 167 174 168 175 /** 169 * appends a {@link Relation} sid to the list of ids which will be fetched from the server.176 * appends a {@link Relation} id to the list of ids which will be fetched from the server. 170 177 * 171 178 * @param relation the relation (ignored, if null) 172 179 * @return this 173 *174 180 */ 175 181 protected MultiFetchServerObjectReader appendRelation(Relation relation) { … … 192 198 } 193 199 200 /** 201 * appends an {@link OsmPrimitive} to the list of ids which will be fetched from the server. 202 * @param primitive the primitive 203 * @return this 204 */ 194 205 public MultiFetchServerObjectReader append(OsmPrimitive primitive) { 195 if ( OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.NODE))196 return appendNode((Node)primitive);197 else if (OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.WAY))198 return appendWay((Way)primitive);199 else if (OsmPrimitiveType.from(primitive).equals(OsmPrimitiveType.RELATION))200 return appendRelation((Relation)primitive);201 206 if (primitive != null) { 207 switch (OsmPrimitiveType.from(primitive)) { 208 case NODE: return appendNode((Node)primitive); 209 case WAY: return appendWay((Way)primitive); 210 case RELATION: return appendRelation((Relation)primitive); 211 } 212 } 202 213 return this; 203 214 } … … 209 220 * @return this 210 221 * 211 * @see #append(Node) 212 * @see #append(Way) 213 * @see #append(Relation) 214 * 222 * @see #append(OsmPrimitive) 215 223 */ 216 224 public MultiFetchServerObjectReader append(Collection<? extends OsmPrimitive> primitives) { … … 235 243 if (ids.size() > MAX_IDS_PER_REQUEST) { 236 244 Iterator<Long> it = ids.iterator(); 237 for (int i =0;i<MAX_IDS_PER_REQUEST;i++) {245 for (int i=0; i<MAX_IDS_PER_REQUEST; i++) { 238 246 pkg.add(it.next()); 239 247 } … … 250 258 * {@link OsmPrimitiveType}. 251 259 * 252 * @param type the type260 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 253 261 * @param idPackage the package of ids 254 262 * @return the request string 255 263 */ 256 protected String buildRequestString(OsmPrimitiveType type, Set<Long> idPackage) {264 protected static String buildRequestString(OsmPrimitiveType type, Set<Long> idPackage) { 257 265 StringBuilder sb = new StringBuilder(); 258 266 sb.append(type.getAPIName()).append("s?") … … 260 268 261 269 Iterator<Long> it = idPackage.iterator(); 262 for (int i=0; i< idPackage.size();i++) {270 for (int i=0; i<idPackage.size(); i++) { 263 271 sb.append(it.next()); 264 272 if (i < idPackage.size()-1) { … … 273 281 * {@link OsmPrimitiveType}. 274 282 * 275 * @param type the type283 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 276 284 * @param id the id 277 285 * @return the request string 278 286 */ 279 protected String buildRequestString(OsmPrimitiveType type, long id) {287 protected static String buildRequestString(OsmPrimitiveType type, long id) { 280 288 StringBuilder sb = new StringBuilder(); 281 289 sb.append(type.getAPIName()).append("s?") … … 283 291 .append(id); 284 292 return sb.toString(); 285 }286 287 /**288 * invokes a Multi Get for a set of ids and a given {@link OsmPrimitiveType}.289 * The retrieved primitives are merged to {@link #outputDataSet}.290 *291 * @param type the type292 * @param pkg the package of ids293 * @exception OsmTransferException thrown if an error occurs while communicating with the API server294 *295 */296 protected void multiGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor) throws OsmTransferException {297 String request = buildRequestString(type, pkg);298 final InputStream in = getInputStream(request, NullProgressMonitor.INSTANCE);299 if (in == null) return;300 progressMonitor.subTask(tr("Downloading OSM data..."));301 try {302 DataSet loaded = OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(pkg.size(), false));303 rememberNodesOfIncompleteWaysToLoad(loaded);304 merge(loaded);305 } catch(Exception e) {306 throw new OsmTransferException(e);307 }308 }309 310 /**311 * invokes a Multi Get for a single id and a given {@link OsmPrimitiveType}.312 * The retrieved primitive is merged to {@link #outputDataSet}.313 *314 * @param type the type315 * @param id the id316 * @exception OsmTransferException thrown if an error occurs while communicating with the API server317 *318 */319 protected void singleGetId(OsmPrimitiveType type, long id, ProgressMonitor progressMonitor) throws OsmTransferException {320 String request = buildRequestString(type, id);321 final InputStream in = getInputStream(request, NullProgressMonitor.INSTANCE);322 if (in == null)323 return;324 progressMonitor.subTask(tr("Downloading OSM data..."));325 try {326 DataSet loaded = OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false));327 rememberNodesOfIncompleteWaysToLoad(loaded);328 merge(loaded);329 } catch(Exception e) {330 throw new OsmTransferException(e);331 }332 }333 334 /**335 * invokes a sequence of Multi Gets for individual ids in a set of ids and a given {@link OsmPrimitiveType}.336 * The retrieved primitives are merged to {@link #outputDataSet}.337 *338 * This method is used if one of the ids in pkg doesn't exist (the server replies with return code 404).339 * If the set is fetched with this method it is possible to find out which of the ids doesn't exist.340 * Unfortunatelly, the server does not provide an error header or an error body for a 404 reply.341 *342 * @param type the type343 * @param pkg the set of ids344 * @exception OsmTransferException thrown if an error occurs while communicating with the API server345 *346 */347 protected void singleGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor) throws OsmTransferException {348 for (long id : pkg) {349 try {350 String msg = "";351 switch(type) {352 case NODE: msg = tr("Fetching node with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;353 case WAY: msg = tr("Fetching way with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;354 case RELATION: msg = tr("Fetching relation with id {0} from ''{1}''", id, OsmApi.getOsmApi().getBaseUrl()); break;355 }356 progressMonitor.setCustomText(msg);357 singleGetId(type, id, progressMonitor);358 } catch(OsmApiException e) {359 if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {360 System.out.println(tr("Server replied with response code 404 for id {0}. Skipping.", Long.toString(id)));361 missingPrimitives.add(new SimplePrimitiveId(id, type));362 continue;363 }364 throw e;365 }366 }367 293 } 368 294 … … 383 309 * 384 310 * @param from the other dataset 385 *386 311 */ 387 312 protected void merge(DataSet from) { … … 394 319 * 395 320 * @param ids the set of ids 396 * @param type the type397 * @ exception OsmTransferException thrown if an error occurs while communicating with the API server398 */ 399 protected void fetchPrimitives(Set<Long> ids, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException {321 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 322 * @throws OsmTransferException if an error occurs while communicating with the API server 323 */ 324 protected void fetchPrimitives(Set<Long> ids, OsmPrimitiveType type, ProgressMonitor progressMonitor) throws OsmTransferException { 400 325 String msg = ""; 401 switch(type) { 402 case NODE: msg = tr("Fetching a package of nodes from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break; 403 case WAY: msg = tr("Fetching a package of ways from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break; 404 case RELATION: msg = tr("Fetching a package of relations from ''{0}''", OsmApi.getOsmApi().getBaseUrl()); break; 326 String baseUrl = OsmApi.getOsmApi().getBaseUrl(); 327 switch (type) { 328 case NODE: msg = tr("Fetching a package of nodes from ''{0}''", baseUrl); break; 329 case WAY: msg = tr("Fetching a package of ways from ''{0}''", baseUrl); break; 330 case RELATION: msg = tr("Fetching a package of relations from ''{0}''", baseUrl); break; 405 331 } 406 332 progressMonitor.setTicksCount(ids.size()); 407 333 progressMonitor.setTicks(0); 334 // The complete set containg all primitives to fetch 408 335 Set<Long> toFetch = new HashSet<Long>(ids); 409 while(! toFetch.isEmpty() && !isCanceled()) { 410 Set<Long> pkg = extractIdPackage(toFetch); 336 // Build a list of fetchers that will download smaller sets containing only MAX_IDS_PER_REQUEST (200) primitives each. 337 // we will run up to MAX_DOWNLOAD_THREADS concurrent fetchers. 338 int threadsNumber = Main.pref.getInteger("osm.download.threads", OsmApi.MAX_DOWNLOAD_THREADS); 339 threadsNumber = Math.min(Math.max(threadsNumber, 1), OsmApi.MAX_DOWNLOAD_THREADS); 340 Executor exec = Executors.newFixedThreadPool(threadsNumber); 341 CompletionService<FetchResult> ecs = new ExecutorCompletionService<FetchResult>(exec); 342 List<Future<FetchResult>> jobs = new ArrayList<Future<FetchResult>>(); 343 while (!toFetch.isEmpty()) { 344 jobs.add(ecs.submit(new Fetcher(type, extractIdPackage(toFetch), progressMonitor))); 345 } 346 // Run the fetchers 347 for (int i = 0; i < jobs.size() && !isCanceled(); i++) { 411 348 progressMonitor.subTask(msg + "... " + progressMonitor.getTicks() + "/" + progressMonitor.getTicksCount()); 412 349 try { 413 multiGetIdPackage(type, pkg, progressMonitor); 414 } catch(OsmApiException e) { 415 if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { 416 System.out.println(tr("Server replied with response code 404, retrying with an individual request for each object.")); 417 singleGetIdPackage(type, pkg, progressMonitor); 418 } else 419 throw e; 350 FetchResult result = ecs.take().get(); 351 if (result.missingPrimitives != null) { 352 missingPrimitives.addAll(result.missingPrimitives); 353 } 354 if (result.dataSet != null && !isCanceled()) { 355 rememberNodesOfIncompleteWaysToLoad(result.dataSet); 356 merge(result.dataSet); 357 } 358 } catch (InterruptedException e) { 359 e.printStackTrace(); 360 } catch (ExecutionException e) { 361 e.printStackTrace(); 362 } 363 } 364 // Cancel requests if the user choosed to 365 if (isCanceled()) { 366 for (Future<FetchResult> job : jobs) { 367 job.cancel(true); 420 368 } 421 369 } … … 433 381 * 434 382 * @return the parsed data 435 * @ exception OsmTransferException thrown if an error occurs while communicating with the API server383 * @throws OsmTransferException if an error occurs while communicating with the API server 436 384 * @see #getMissingPrimitives() 437 385 * … … 443 391 try { 444 392 missingPrimitives = new HashSet<PrimitiveId>(); 445 if (isCanceled()) return null;393 if (isCanceled()) return null; 446 394 fetchPrimitives(ways,OsmPrimitiveType.WAY, progressMonitor); 447 if (isCanceled()) return null;395 if (isCanceled()) return null; 448 396 fetchPrimitives(nodes,OsmPrimitiveType.NODE, progressMonitor); 449 if (isCanceled()) return null;397 if (isCanceled()) return null; 450 398 fetchPrimitives(relations,OsmPrimitiveType.RELATION, progressMonitor); 451 399 if (outputDataSet != null) { … … 468 416 return missingPrimitives; 469 417 } 418 419 /** 420 * The class holding the results given by {@link Fetcher}. 421 * It is only a wrapper of the resulting {@link DataSet} and the collection of {@link PrimitiveId} that could not have been loaded. 422 */ 423 protected static class FetchResult { 424 425 /** 426 * The resulting data set 427 */ 428 public final DataSet dataSet; 429 430 /** 431 * The collection of primitive ids that could not have been loaded 432 */ 433 public final Set<PrimitiveId> missingPrimitives; 434 435 /** 436 * Constructs a {@code FetchResult} 437 * @param dataSet The resulting data set 438 * @param missingPrimitives The collection of primitive ids that could not have been loaded 439 */ 440 public FetchResult(DataSet dataSet, Set<PrimitiveId> missingPrimitives) { 441 this.dataSet = dataSet; 442 this.missingPrimitives = missingPrimitives; 443 } 444 } 445 446 /** 447 * The class that actually download data from OSM API. Several instances of this class are used by {@link MultiFetchServerObjectReader} (one per set of primitives to fetch). 448 * The inheritance of {@link OsmServerReader} is only explained by the need to have a distinct OSM connection by {@code Fetcher} instance. 449 * @see FetchResult 450 */ 451 protected static class Fetcher extends OsmServerReader implements Callable<FetchResult> { 452 453 private final Set<Long> pkg; 454 private final OsmPrimitiveType type; 455 private final ProgressMonitor progressMonitor; 456 457 /** 458 * Constructs a {@code Fetcher} 459 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 460 * @param idsPackage The set of primitives ids to fetch 461 * @param progressMonitor The progress monitor 462 */ 463 public Fetcher(OsmPrimitiveType type, Set<Long> idsPackage, ProgressMonitor progressMonitor) { 464 this.pkg = idsPackage; 465 this.type = type; 466 this.progressMonitor = progressMonitor; 467 } 468 469 @Override 470 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 471 // This method is implemented because of the OsmServerReader inheritance, but not used, as the main target of this class is the call() method. 472 return fetch(progressMonitor).dataSet; 473 } 474 475 @Override 476 public FetchResult call() throws Exception { 477 return fetch(progressMonitor); 478 } 479 480 /** 481 * fetches the requested primitives and updates the specified progress monitor. 482 * @param progressMonitor the progress monitor 483 * @return the {@link FetchResult} of this operation 484 * @throws OsmTransferException if an error occurs while communicating with the API server 485 */ 486 protected FetchResult fetch(ProgressMonitor progressMonitor) throws OsmTransferException { 487 try { 488 return multiGetIdPackage(type, pkg, progressMonitor); 489 } catch (OsmApiException e) { 490 if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { 491 System.out.println(tr("Server replied with response code 404, retrying with an individual request for each object.")); 492 return singleGetIdPackage(type, pkg, progressMonitor); 493 } else { 494 throw e; 495 } 496 } 497 } 498 499 /** 500 * invokes a Multi Get for a set of ids and a given {@link OsmPrimitiveType}. 501 * The retrieved primitives are merged to {@link #outputDataSet}. 502 * 503 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 504 * @param pkg the package of ids 505 * @return the {@link FetchResult} of this operation 506 * @throws OsmTransferException if an error occurs while communicating with the API server 507 */ 508 protected FetchResult multiGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor) throws OsmTransferException { 509 String request = buildRequestString(type, pkg); 510 final InputStream in = getInputStream(request, NullProgressMonitor.INSTANCE); 511 if (in == null) return null; 512 progressMonitor.subTask(tr("Downloading OSM data...")); 513 try { 514 return new FetchResult(OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(pkg.size(), false)), null); 515 } catch (Exception e) { 516 throw new OsmTransferException(e); 517 } 518 } 519 520 /** 521 * invokes a Multi Get for a single id and a given {@link OsmPrimitiveType}. 522 * The retrieved primitive is merged to {@link #outputDataSet}. 523 * 524 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 525 * @param id the id 526 * @return the {@link DataSet} resulting of this operation 527 * @throws OsmTransferException if an error occurs while communicating with the API server 528 */ 529 protected DataSet singleGetId(OsmPrimitiveType type, long id, ProgressMonitor progressMonitor) throws OsmTransferException { 530 String request = buildRequestString(type, id); 531 final InputStream in = getInputStream(request, NullProgressMonitor.INSTANCE); 532 if (in == null) return null; 533 progressMonitor.subTask(tr("Downloading OSM data...")); 534 try { 535 return OsmReader.parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false)); 536 } catch (Exception e) { 537 throw new OsmTransferException(e); 538 } 539 } 540 541 /** 542 * invokes a sequence of Multi Gets for individual ids in a set of ids and a given {@link OsmPrimitiveType}. 543 * The retrieved primitives are merged to {@link #outputDataSet}. 544 * 545 * This method is used if one of the ids in pkg doesn't exist (the server replies with return code 404). 546 * If the set is fetched with this method it is possible to find out which of the ids doesn't exist. 547 * Unfortunately, the server does not provide an error header or an error body for a 404 reply. 548 * 549 * @param type The primitive type. Must be one of {@link OsmPrimitiveType#NODE NODE}, {@link OsmPrimitiveType#WAY WAY}, {@link OsmPrimitiveType#RELATION RELATION} 550 * @param pkg the set of ids 551 * @return the {@link FetchResult} of this operation 552 * @throws OsmTransferException if an error occurs while communicating with the API server 553 */ 554 protected FetchResult singleGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor) throws OsmTransferException { 555 FetchResult result = new FetchResult(new DataSet(), new HashSet<PrimitiveId>()); 556 String baseUrl = OsmApi.getOsmApi().getBaseUrl(); 557 for (long id : pkg) { 558 try { 559 String msg = ""; 560 switch (type) { 561 case NODE: msg = tr("Fetching node with id {0} from ''{1}''", id, baseUrl); break; 562 case WAY: msg = tr("Fetching way with id {0} from ''{1}''", id, baseUrl); break; 563 case RELATION: msg = tr("Fetching relation with id {0} from ''{1}''", id, baseUrl); break; 564 } 565 progressMonitor.setCustomText(msg); 566 result.dataSet.mergeFrom(singleGetId(type, id, progressMonitor)); 567 } catch (OsmApiException e) { 568 if (e.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { 569 System.out.println(tr("Server replied with response code 404 for id {0}. Skipping.", Long.toString(id))); 570 result.missingPrimitives.add(new SimplePrimitiveId(id, type)); 571 } else { 572 throw e; 573 } 574 } 575 } 576 return result; 577 } 578 } 470 579 }
Note:
See TracChangeset
for help on using the changeset viewer.