001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.util.Map; 005import java.util.concurrent.ThreadPoolExecutor; 006import java.util.concurrent.TimeUnit; 007 008import org.apache.commons.jcs.access.behavior.ICacheAccess; 009import org.openstreetmap.gui.jmapviewer.Tile; 010import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 011import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 012import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 013import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 014import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 015import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 016import org.openstreetmap.josm.data.cache.HostLimitQueue; 017import org.openstreetmap.josm.data.preferences.IntegerProperty; 018import org.openstreetmap.josm.tools.CheckParameterUtil; 019import org.openstreetmap.josm.tools.Utils; 020 021/** 022 * Wrapper class that bridges between JCS cache and Tile Loaders 023 * 024 * @author Wiktor Niesiobędzki 025 */ 026public class TMSCachedTileLoader implements TileLoader, CachedTileLoader { 027 028 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 029 protected final int connectTimeout; 030 protected final int readTimeout; 031 protected final Map<String, String> headers; 032 protected final TileLoaderListener listener; 033 034 /** 035 * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS 036 */ 037 038 public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25); 039 040 /** 041 * Limit definition for per host concurrent connections 042 */ 043 public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6); 044 045 /** 046 * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS 047 * and for TMS imagery 048 */ 049 private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS-downloader-%d"); 050 051 private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 052 053 /** 054 * Constructor 055 * @param listener called when tile loading has finished 056 * @param cache of the cache 057 * @param connectTimeout to remote resource 058 * @param readTimeout to remote resource 059 * @param headers HTTP headers to be sent along with request 060 */ 061 public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 062 int connectTimeout, int readTimeout, Map<String, String> headers) { 063 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 064 this.cache = cache; 065 this.connectTimeout = connectTimeout; 066 this.readTimeout = readTimeout; 067 this.headers = headers; 068 this.listener = listener; 069 } 070 071 /** 072 * @param nameFormat see {@link Utils#newThreadFactory(String, int)} 073 * @param workers number of worker thread to keep 074 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue 075 */ 076 public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers) { 077 HostLimitQueue workQueue = new HostLimitQueue(HOST_LIMIT.get().intValue()); 078 ThreadPoolExecutor executor = new ThreadPoolExecutor( 079 0, // 0 so for unused thread pools threads will eventually die, freeing also the threadpool 080 workers, // do not this number of threads 081 300, // keepalive for thread 082 TimeUnit.SECONDS, 083 workQueue, 084 Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY) 085 ); 086 workQueue.setExecutor(executor); 087 return executor; 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 @Override 130 public boolean hasOutstandingTasks() { 131 return downloadExecutor.getTaskCount() > downloadExecutor.getCompletedTaskCount(); 132 } 133 134 /** 135 * Sets the download executor that will be used to download tiles instead of default one. 136 * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate 137 * queue from default. 138 * 139 * @param downloadExecutor download executor that will be used to download tiles 140 */ 141 public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) { 142 this.downloadExecutor = downloadExecutor; 143 } 144 145 /** 146 * @return download executor that is used by this factory 147 */ 148 public ThreadPoolExecutor getDownloadExecutor() { 149 return downloadExecutor; 150 } 151}