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}