001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.io.IOException; 005import java.util.Map; 006import java.util.concurrent.ThreadPoolExecutor; 007import java.util.concurrent.TimeUnit; 008 009import org.apache.commons.jcs.access.behavior.ICacheAccess; 010import org.openstreetmap.gui.jmapviewer.Tile; 011import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 012import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 013import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 014import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 015import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 016import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 017import org.openstreetmap.josm.data.cache.HostLimitQueue; 018import org.openstreetmap.josm.data.preferences.IntegerProperty; 019import org.openstreetmap.josm.tools.CheckParameterUtil; 020import org.openstreetmap.josm.tools.Utils; 021 022/** 023 * Wrapper class that bridges between JCS cache and Tile Loaders 024 * 025 * @author Wiktor Niesiobędzki 026 */ 027public class TMSCachedTileLoader implements TileLoader, CachedTileLoader { 028 029 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 030 protected final int connectTimeout; 031 protected final int readTimeout; 032 protected final Map<String, String> headers; 033 protected final TileLoaderListener listener; 034 035 /** 036 * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS 037 */ 038 039 public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25); 040 041 /** 042 * Limit definition for per host concurrent connections 043 */ 044 public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6); 045 046 /** 047 * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS 048 * and for TMS imagery 049 */ 050 private static ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS-downloader-%d"); 051 052 053 private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 054 055 /** 056 * Constructor 057 * @param listener called when tile loading has finished 058 * @param cache of the cache 059 * @param connectTimeout to remote resource 060 * @param readTimeout to remote resource 061 * @param headers HTTP headers to be sent along with request 062 * @throws IOException when cache initialization fails 063 */ 064 public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 065 int connectTimeout, int readTimeout, Map<String, String> headers) throws IOException { 066 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 067 this.cache = cache; 068 this.connectTimeout = connectTimeout; 069 this.readTimeout = readTimeout; 070 this.headers = headers; 071 this.listener = listener; 072 } 073 074 /** 075 * @param nameFormat see {@link Utils#newThreadFactory(String, int)} 076 * @param workers number of worker thread to keep 077 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue 078 */ 079 public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers) { 080 return new ThreadPoolExecutor( 081 workers, // keep the thread number constant 082 workers, // do not this number of threads 083 30, // keepalive for thread 084 TimeUnit.SECONDS, 085 new HostLimitQueue(HOST_LIMIT.get().intValue()), 086 Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY) 087 ); 088 } 089 090 /** 091 * @param name name of threads 092 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue, with default number of threads 093 */ 094 public static ThreadPoolExecutor getNewThreadPoolExecutor(String name) { 095 return getNewThreadPoolExecutor(name, THREAD_LIMIT.get().intValue()); 096 } 097 098 @Override 099 public TileJob createTileLoaderJob(Tile tile) { 100 return new TMSCachedTileLoaderJob(listener, tile, cache, 101 connectTimeout, readTimeout, headers, getDownloadExecutor()); 102 } 103 104 @Override 105 public void clearCache(TileSource source) { 106 this.cache.remove(source.getName() + ':'); 107 } 108 109 /** 110 * @return cache statistics as string 111 */ 112 public String getStats() { 113 return cache.getStats(); 114 } 115 116 /** 117 * cancels all outstanding tasks in the queue. This rollbacks the state of the tiles in the queue 118 * to loading = false / loaded = false 119 */ 120 @Override 121 public void cancelOutstandingTasks() { 122 for (Runnable r: downloadExecutor.getQueue()) { 123 if (downloadExecutor.remove(r) && r instanceof TMSCachedTileLoaderJob) { 124 ((TMSCachedTileLoaderJob) r).handleJobCancellation(); 125 } 126 } 127 } 128 129 /** 130 * Sets the download executor that will be used to download tiles instead of default one. 131 * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate 132 * queue from default. 133 * 134 * @param downloadExecutor download executor that will be used to download tiles 135 */ 136 public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) { 137 this.downloadExecutor = downloadExecutor; 138 } 139 140 /** 141 * @return download executor that is used by this factory 142 */ 143 public ThreadPoolExecutor getDownloadExecutor() { 144 return downloadExecutor; 145 } 146}