// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.imagery; import java.util.Map; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.openstreetmap.gui.jmapviewer.Tile; import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; import org.openstreetmap.josm.data.cache.HostLimitQueue; import org.openstreetmap.josm.data.preferences.IntegerProperty; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Utils; /** * Wrapper class that bridges between JCS cache and Tile Loaders * * @author Wiktor Niesiobędzki */ public class TMSCachedTileLoader implements TileLoader, CachedTileLoader { protected final ICacheAccess<String, BufferedImageCacheEntry> cache; protected final int connectTimeout; protected final int readTimeout; protected final Map<String, String> headers; protected final TileLoaderListener listener; /** * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS */ public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25); /** * Limit definition for per host concurrent connections */ public static final IntegerProperty HOST_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobsperhost", 6); /** * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS * and for TMS imagery */ private static ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS-downloader-%d"); private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; /** * Constructor * @param listener called when tile loading has finished * @param cache of the cache * @param connectTimeout to remote resource * @param readTimeout to remote resource * @param headers HTTP headers to be sent along with request */ public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, int connectTimeout, int readTimeout, Map<String, String> headers) { CheckParameterUtil.ensureParameterNotNull(cache, "cache"); this.cache = cache; this.connectTimeout = connectTimeout; this.readTimeout = readTimeout; this.headers = headers; this.listener = listener; } /** * @param nameFormat see {@link Utils#newThreadFactory(String, int)} * @param workers number of worker thread to keep * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue */ public static ThreadPoolExecutor getNewThreadPoolExecutor(String nameFormat, int workers) { HostLimitQueue workQueue = new HostLimitQueue(HOST_LIMIT.get().intValue()); ThreadPoolExecutor executor = new ThreadPoolExecutor( 0, // 0 so for unused thread pools threads will eventually die, freeing also the threadpool workers, // do not this number of threads 300, // keepalive for thread TimeUnit.SECONDS, workQueue, Utils.newThreadFactory(nameFormat, Thread.NORM_PRIORITY) ); workQueue.setExecutor(executor); return executor; } /** * @param name name of threads * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue, with default number of threads */ public static ThreadPoolExecutor getNewThreadPoolExecutor(String name) { return getNewThreadPoolExecutor(name, THREAD_LIMIT.get().intValue()); } @Override public TileJob createTileLoaderJob(Tile tile) { return new TMSCachedTileLoaderJob(listener, tile, cache, connectTimeout, readTimeout, headers, getDownloadExecutor()); } @Override public void clearCache(TileSource source) { this.cache.remove(source.getName() + ':'); } /** * @return cache statistics as string */ public String getStats() { return cache.getStats(); } /** * cancels all outstanding tasks in the queue. This rollbacks the state of the tiles in the queue * to loading = false / loaded = false */ @Override public void cancelOutstandingTasks() { for (Runnable r: downloadExecutor.getQueue()) { if (downloadExecutor.remove(r) && r instanceof TMSCachedTileLoaderJob) { ((TMSCachedTileLoaderJob) r).handleJobCancellation(); } } } /** * Sets the download executor that will be used to download tiles instead of default one. * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate * queue from default. * * @param downloadExecutor download executor that will be used to download tiles */ public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) { this.downloadExecutor = downloadExecutor; } /** * @return download executor that is used by this factory */ public ThreadPoolExecutor getDownloadExecutor() { return downloadExecutor; } }