001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.util.concurrent.ThreadPoolExecutor; 005import java.util.concurrent.TimeUnit; 006 007import org.apache.commons.jcs3.access.behavior.ICacheAccess; 008import org.apache.commons.jcs3.engine.behavior.ICache; 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 TileLoaderListener listener; 030 031 /** 032 * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS 033 */ 034 035 public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25); 036 037 /** 038 * Limit definition for per host concurrent connections 039 */ 040 public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6); 041 042 /** 043 * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS 044 * and for TMS imagery 045 */ 046 private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS-downloader-%d"); 047 048 private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 049 protected final TileJobOptions options; 050 051 /** 052 * Constructor 053 * @param listener called when tile loading has finished 054 * @param cache of the cache 055 * @param options tile job options 056 */ 057 public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 058 TileJobOptions options) { 059 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 060 this.cache = cache; 061 this.options = options; 062 this.listener = listener; 063 } 064 065 /** 066 * Returns a new {@link ThreadPoolExecutor}. 067 * @param nameFormat see {@link Utils#newThreadFactory(String, int)} 068 * @param workers number of worker thread to keep 069 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue 070 */ 071 public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers) { 072 return getNewThreadPoolExecutor(nameFormat, workers, HOST_LIMIT.get().intValue()); 073 } 074 075 /** 076 * Returns a new {@link ThreadPoolExecutor}. 077 * @param nameFormat see {@link Utils#newThreadFactory(String, int)} 078 * @param workers number of worker thread to keep 079 * @param hostLimit number of concurrent downloads per host allowed 080 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue 081 */ 082 public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers, int hostLimit) { 083 return new ThreadPoolExecutor( 084 workers, // keep core pool the same size as max, as we use unbounded queue so there will 085 workers, // be never more threads than corePoolSize 086 300, // keep alive for thread 087 TimeUnit.SECONDS, 088 new HostLimitQueue(hostLimit), 089 Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY) 090 ); 091 } 092 093 /** 094 * Returns a new {@link ThreadPoolExecutor}. 095 * @param name name of threads 096 * @return new ThreadPoolExecutor that will use a {@link HostLimitQueue} based queue, with default number of threads 097 */ 098 public static ThreadPoolExecutor getNewThreadPoolExecutor(String name) { 099 return getNewThreadPoolExecutor(name, THREAD_LIMIT.get().intValue()); 100 } 101 102 @Override 103 public TileJob createTileLoaderJob(Tile tile) { 104 return new TMSCachedTileLoaderJob( 105 listener, 106 tile, 107 cache, 108 options, 109 getDownloadExecutor()); 110 } 111 112 @Override 113 public void clearCache(TileSource source) { 114 this.cache.remove(source.getName() + ICache.NAME_COMPONENT_DELIMITER); 115 } 116 117 /** 118 * Returns cache statistics as string. 119 * @return cache statistics as string 120 */ 121 public String getStats() { 122 return cache.getStats(); 123 } 124 125 /** 126 * cancels all outstanding tasks in the queue. This rollbacks the state of the tiles in the queue 127 * to loading = false / loaded = false 128 */ 129 @Override 130 public void cancelOutstandingTasks() { 131 for (Runnable r: downloadExecutor.getQueue()) { 132 if (downloadExecutor.remove(r) && r instanceof TMSCachedTileLoaderJob) { 133 ((TMSCachedTileLoaderJob) r).handleJobCancellation(); 134 } 135 } 136 } 137 138 @Override 139 public boolean hasOutstandingTasks() { 140 return downloadExecutor.getTaskCount() > downloadExecutor.getCompletedTaskCount(); 141 } 142 143 /** 144 * Sets the download executor that will be used to download tiles instead of default one. 145 * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate 146 * queue from default. 147 * 148 * @param downloadExecutor download executor that will be used to download tiles 149 */ 150 public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) { 151 this.downloadExecutor = downloadExecutor; 152 } 153 154 /** 155 * Returns download executor that is used by this factory. 156 * @return download executor that is used by this factory 157 */ 158 public ThreadPoolExecutor getDownloadExecutor() { 159 return downloadExecutor; 160 } 161 162 /** 163 * Shutdown the job dispatcher provided that it's not the default one 164 */ 165 public void shutdown() { 166 if (!downloadExecutor.equals(DEFAULT_DOWNLOAD_JOB_DISPATCHER)) { 167 cancelOutstandingTasks(); 168 downloadExecutor.shutdown(); 169 } 170 } 171}