Changeset 36145 in osm


Ignore:
Timestamp:
2023-09-18T21:17:28+02:00 (14 months ago)
Author:
taylor.smock
Message:

Fix #23112: Expand visibility of some objects and methods in JCSCachedTileLoaderJob for plugin subclasses

Location:
applications/editors/josm/plugins/pmtiles
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/pmtiles/build.xml

    r36144 r36145  
    88    <property name="commit.message" value="Commit message"/>
    99    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    10     <property name="plugin.main.version" value="18830"/>
     10    <property name="plugin.main.version" value="18831"/>
    1111
    1212    <!-- Configure these properties (replace "..." accordingly).
  • applications/editors/josm/plugins/pmtiles/src/main/java/org/openstreetmap/josm/plugins/pmtiles/gui/layers/JCSCachedTileLoaderJob.java

    r36112 r36145  
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.plugins.pmtiles.gui.layers;
    3 
    4 import java.io.File;
    5 import java.io.FileNotFoundException;
    6 import java.io.IOException;
    7 import java.io.InputStream;
    8 import java.lang.annotation.Documented;
    9 import java.lang.annotation.ElementType;
    10 import java.lang.annotation.Target;
    11 import java.net.HttpURLConnection;
    12 import java.net.URL;
    13 import java.nio.file.Files;
    14 import java.security.SecureRandom;
    15 import java.util.Collections;
    16 import java.util.List;
    17 import java.util.Map;
    18 import java.util.Set;
    19 import java.util.concurrent.ConcurrentHashMap;
    20 import java.util.concurrent.ConcurrentMap;
    21 import java.util.concurrent.LinkedBlockingDeque;
    22 import java.util.concurrent.ThreadPoolExecutor;
    23 import java.util.concurrent.TimeUnit;
    24 import java.util.regex.Matcher;
    25 
    26 import org.openstreetmap.josm.data.cache.CacheEntry;
    27 import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
    28 import org.openstreetmap.josm.data.cache.ICachedLoaderJob;
    29 import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
    30 import org.openstreetmap.josm.data.cache.ICachedLoaderListener.LoadResult;
    31 import org.openstreetmap.josm.data.imagery.TileJobOptions;
    32 import org.openstreetmap.josm.data.preferences.IntegerProperty;
    33 import org.openstreetmap.josm.tools.CheckParameterUtil;
    34 import org.openstreetmap.josm.tools.HttpClient;
    35 import org.openstreetmap.josm.tools.Logging;
    36 import org.openstreetmap.josm.tools.Utils;
    37 
    38 import org.apache.commons.jcs3.access.behavior.ICacheAccess;
    39 import org.apache.commons.jcs3.engine.behavior.ICacheElement;
    40 
    41 /**
    42  * Generic loader for HTTP based tiles. Uses custom attribute, to check, if entry has expired
    43  * according to HTTP headers sent with tile. If so, it tries to verify using Etags
    44  * or If-Modified-Since / Last-Modified.
    45  * <p>
    46  * If the tile is not valid, it will try to download it from remote service and put it
    47  * to cache. If remote server will fail it will try to use stale entry.
    48  * <p>
    49  * This class will keep only one Job running for specified tile. All others will just finish, but
    50  * listeners will be gathered and notified, once download job will be finished
    51  *
    52  * @author Wiktor Niesiobędzki
    53  * @param <K> cache entry key type
    54  * @param <V> cache value type
    55  * @since 8168 (in JOSM). Copied to PMTilesPlugin to make some methods overridable. Methods modified are annotated with
    56  * {@link #ModifiedFromJosm}
    57  */
    58 public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements ICachedLoaderJob<K> {
    59     @Documented
    60     @Target({ElementType.METHOD, ElementType.FIELD})
    61     @interface ModifiedFromJosm {
    62         /**
    63          * What changed from JOSM
    64          * @return The reason or changes
    65          */
    66         String value() default "";
    67     }
    68     protected static final long DEFAULT_EXPIRE_TIME = TimeUnit.DAYS.toMillis(7);
    69     // Limit for the max-age value send by the server.
    70     protected static final long EXPIRE_TIME_SERVER_LIMIT = TimeUnit.DAYS.toMillis(28);
    71     // Absolute expire time limit. Cached tiles that are older will not be used,
    72     // even if the refresh from the server fails.
    73     protected static final long ABSOLUTE_EXPIRE_TIME_LIMIT = TimeUnit.DAYS.toMillis(365);
    74 
    75     /**
    76      * maximum download threads that will be started
    77      */
    78     public static final IntegerProperty THREAD_LIMIT = new IntegerProperty("cache.jcs.max_threads", 10);
    79 
    80     /*
    81      * ThreadPoolExecutor starts new threads, until THREAD_LIMIT is reached. Then it puts tasks into LinkedBlockingDeque.
    82      *
    83      * The queue works FIFO, so one needs to take care about ordering of the entries submitted
    84      *
    85      * There is no point in canceling tasks, that are already taken by worker threads (if we made so much effort, we can at least cache
    86      * the response, so later it could be used). We could actually cancel what is in LIFOQueue, but this is a tradeoff between simplicity
    87      * and performance (we do want to have something to offer to worker threads before tasks will be resubmitted by class consumer)
    88      */
    89 
    90     private static final ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = new ThreadPoolExecutor(
    91             1, // we have a small queue, so threads will be quickly started (threads are started only, when queue is full)
    92             THREAD_LIMIT.get(), // do not this number of threads
    93             30, // keepalive for thread
    94             TimeUnit.SECONDS,
    95             // make queue of LIFO type - so recently requested tiles will be loaded first (assuming that these are which user is waiting to see)
    96             new LinkedBlockingDeque<>(),
    97             Utils.newThreadFactory("JCS-downloader-%d", Thread.NORM_PRIORITY)
    98     );
    99 
    100     private static final ConcurrentMap<String, Set<ICachedLoaderListener>> inProgress = new ConcurrentHashMap<>();
    101     private static final ConcurrentMap<String, Boolean> useHead = new ConcurrentHashMap<>();
    102 
    103     protected final long now; // when the job started
    104 
    105     @ModifiedFromJosm("Visibility")
    106     protected final ICacheAccess<K, V> cache;
    107     private ICacheElement<K, V> cacheElement;
    108     protected V cacheData;
    109     protected CacheEntryAttributes attributes;
    110 
    111     // HTTP connection parameters
    112     private final int connectTimeout;
    113     private final int readTimeout;
    114     private final Map<String, String> headers;
    115     private final ThreadPoolExecutor downloadJobExecutor;
    116     private Runnable finishTask;
    117     private boolean force;
    118     private final long minimumExpiryTime;
    119 
    120     /**
    121      * @param cache cache instance that we will work on
    122      * @param options options of the request
    123      * @param downloadJobExecutor that will be executing the jobs
    124      */
    125     protected JCSCachedTileLoaderJob(ICacheAccess<K, V> cache,
    126                                      TileJobOptions options,
    127                                      ThreadPoolExecutor downloadJobExecutor) {
    128         CheckParameterUtil.ensureParameterNotNull(cache, "cache");
    129         this.cache = cache;
    130         this.now = System.currentTimeMillis();
    131         this.connectTimeout = options.getConnectionTimeout();
    132         this.readTimeout = options.getReadTimeout();
    133         this.headers = options.getHeaders();
    134         this.downloadJobExecutor = downloadJobExecutor;
    135         this.minimumExpiryTime = TimeUnit.SECONDS.toMillis(options.getMinimumExpiryTime());
    136     }
    137 
    138     /**
    139      * @param cache cache instance that we will work on
    140      * @param options of the request
    141      */
    142     protected JCSCachedTileLoaderJob(ICacheAccess<K, V> cache,
    143                                      TileJobOptions options) {
    144         this(cache, options, DEFAULT_DOWNLOAD_JOB_DISPATCHER);
    145     }
    146 
    147     private void ensureCacheElement() {
    148         if (cacheElement == null && getCacheKey() != null) {
    149             cacheElement = cache.getCacheElement(getCacheKey());
    150             if (cacheElement != null) {
    151                 attributes = (CacheEntryAttributes) cacheElement.getElementAttributes();
    152                 cacheData = cacheElement.getVal();
    153             }
    154         }
    155     }
    156 
    157     @Override
    158     public V get() {
    159         ensureCacheElement();
    160         return cacheData;
    161     }
    162 
    163     @Override
    164     public void submit(ICachedLoaderListener listener, boolean force) throws IOException {
    165         this.force = force;
    166         boolean first = false;
    167         URL url = getUrl();
    168         String deduplicationKey = null;
    169         if (url != null) {
    170             // url might be null, for example when Bing Attribution is not loaded yet
    171             deduplicationKey = url.toString();
    172         }
    173         if (deduplicationKey == null) {
    174             Logging.warn("No url returned for: {0}, skipping", getCacheKey());
    175             throw new IllegalArgumentException("No url returned");
    176         }
    177         synchronized (this) {
    178             first = !inProgress.containsKey(deduplicationKey);
    179         }
    180         inProgress.computeIfAbsent(deduplicationKey, k -> ConcurrentHashMap.newKeySet()).add(listener);
    181 
    182         if (first || force) {
    183             // submit all jobs to separate thread, so calling thread is not blocked with IO when loading from disk
    184             Logging.debug("JCS - Submitting job for execution for url: {0}", getUrlNoException());
    185             downloadJobExecutor.execute(this);
    186         }
    187     }
    188 
    189     /**
    190      * This method is run when job has finished
    191      */
    192     protected void executionFinished() {
    193         if (finishTask != null) {
    194             finishTask.run();
    195         }
    196     }
    197 
    198     /**
    199      * Checks if object from cache has sufficient data to be returned.
    200      * @return {@code true} if object from cache has sufficient data to be returned
    201      */
    202     protected boolean isObjectLoadable() {
    203         if (cacheData == null) {
    204             return false;
    205         }
    206         return cacheData.getContent().length > 0;
    207     }
    208 
    209     /**
    210      * Simple implementation. All errors should be cached as empty. Though some JDK (JDK8 on Windows for example)
    211      * doesn't return 4xx error codes, instead they do throw an FileNotFoundException or IOException
    212      * @param headerFields headers sent by server
    213      * @param responseCode http status code
    214      *
    215      * @return true if we should put empty object into cache, regardless of what remote resource has returned
    216      */
    217     protected boolean cacheAsEmpty(Map<String, List<String>> headerFields, int responseCode) {
    218         return attributes.getResponseCode() < 500;
    219     }
    220 
    221     /**
    222      * Returns key under which discovered server settings will be kept.
    223      * @return key under which discovered server settings will be kept
    224      */
    225     protected String getServerKey() {
    226         try {
    227             return getUrl().getHost();
    228         } catch (IOException e) {
    229             Logging.trace(e);
    230             return null;
    231         }
    232     }
    233 
    234     @Override
    235     public void run() {
    236         final Thread currentThread = Thread.currentThread();
    237         final String oldName = currentThread.getName();
    238         currentThread.setName("JCS Downloading: " + getUrlNoException());
    239         Logging.debug("JCS - starting fetch of url: {0} ", getUrlNoException());
    240         ensureCacheElement();
    241         try {
    242             // try to fetch from cache
    243             if (!force && cacheElement != null && isCacheElementValid() && isObjectLoadable()) {
    244                 // we got something in cache, and it's valid, so lets return it
    245                 Logging.debug("JCS - Returning object from cache: {0}", getCacheKey());
    246                 finishLoading(LoadResult.SUCCESS);
    247                 return;
    248             }
    249 
    250             // try to load object from remote resource
    251             if (loadObject()) {
    252                 finishLoading(LoadResult.SUCCESS);
    253             } else {
    254                 // if loading failed - check if we can return stale entry
    255                 if (isObjectLoadable()) {
    256                     // try to get stale entry in cache
    257                     finishLoading(LoadResult.SUCCESS);
    258                     Logging.debug("JCS - found stale object in cache: {0}", getUrlNoException());
    259                 } else {
    260                     // failed completely
    261                     finishLoading(LoadResult.FAILURE);
    262                 }
    263             }
    264         } finally {
    265             executionFinished();
    266             currentThread.setName(oldName);
    267         }
    268     }
    269 
    270     private void finishLoading(LoadResult result) {
    271         Set<ICachedLoaderListener> listeners;
    272         try {
    273             listeners = inProgress.remove(getUrl().toString());
    274         } catch (IOException e) {
    275             listeners = null;
    276             Logging.trace(e);
    277         }
    278         if (listeners == null) {
    279             Logging.warn("Listener not found for URL: {0}. Listener not notified!", getUrlNoException());
    280             return;
    281         }
    282         for (ICachedLoaderListener l: listeners) {
    283             l.loadingFinished(cacheData, attributes, result);
    284         }
    285     }
    286 
    287     protected boolean isCacheElementValid() {
    288         long expires = attributes.getExpirationTime();
    289 
    290         // check by expire date set by server
    291         if (expires != 0L) {
    292             // put a limit to the expire time (some servers send a value
    293             // that is too large)
    294             expires = Math.min(expires, attributes.getCreateTime() + Math.max(EXPIRE_TIME_SERVER_LIMIT, minimumExpiryTime));
    295             if (now > expires) {
    296                 Logging.debug("JCS - Object {0} has expired -> valid to {1}, now is: {2}",
    297                         getUrlNoException(), Long.toString(expires), Long.toString(now));
    298                 return false;
    299             }
    300         } else if (attributes.getLastModification() > 0 &&
    301                 now - attributes.getLastModification() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {
    302             // check by file modification date
    303             Logging.debug("JCS - Object has expired, maximum file age reached {0}", getUrlNoException());
    304             return false;
    305         } else if (now - attributes.getCreateTime() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {
    306             Logging.debug("JCS - Object has expired, maximum time since object creation reached {0}", getUrlNoException());
    307             return false;
    308         }
    309         return true;
    310     }
    311 
    312     /**
    313      * @return true if object was successfully downloaded, false, if there was a loading failure
    314      */
    315     @ModifiedFromJosm("visibility")
    316     protected boolean loadObject() {
    317         if (attributes == null) {
    318             attributes = new CacheEntryAttributes();
    319         }
    320         final URL url = this.getUrlNoException();
    321         if (url == null) {
    322             return false;
    323         }
    324 
    325         if (url.getProtocol().contains("http")) {
    326             return loadObjectHttp();
    327         }
    328         if (url.getProtocol().contains("file")) {
    329             return loadObjectFile(url);
    330         }
    331 
    332         return false;
    333     }
    334 
    335     private boolean loadObjectFile(URL url) {
    336         String fileName = url.toExternalForm();
    337         File file = new File(fileName.substring("file:/".length() - 1));
    338         if (!file.exists()) {
    339             file = new File(fileName.substring("file://".length() - 1));
    340         }
    341         try (InputStream fileInputStream = Files.newInputStream(file.toPath())) {
    342             cacheData = createCacheEntry(Utils.readBytesFromStream(fileInputStream));
    343             cache.put(getCacheKey(), cacheData, attributes);
    344             return true;
    345         } catch (IOException e) {
    346             Logging.error(e);
    347             attributes.setError(e);
    348             attributes.setException(e);
    349         }
    350         return false;
    351     }
    352 
    353     /**
    354      * @return true if object was successfully downloaded via http, false, if there was a loading failure
    355      */
    356     private boolean loadObjectHttp() {
    357         try {
    358             // if we have object in cache, and host doesn't support If-Modified-Since nor If-None-Match
    359             // then just use HEAD request and check returned values
    360             if (isObjectLoadable() &&
    361                     Boolean.TRUE.equals(useHead.get(getServerKey())) &&
    362                     isCacheValidUsingHead()) {
    363                 Logging.debug("JCS - cache entry verified using HEAD request: {0}", getUrl());
    364                 return true;
    365             }
    366 
    367             Logging.debug("JCS - starting HttpClient GET request for URL: {0}", getUrl());
    368             final HttpClient request = getRequest("GET");
    369 
    370             if (isObjectLoadable() &&
    371                     (now - attributes.getLastModification()) <= ABSOLUTE_EXPIRE_TIME_LIMIT) {
    372                 request.setIfModifiedSince(attributes.getLastModification());
    373             }
    374             if (isObjectLoadable() && attributes.getEtag() != null) {
    375                 request.setHeader("If-None-Match", attributes.getEtag());
    376             }
    377 
    378             final HttpClient.Response urlConn = request.connect();
    379 
    380             if (urlConn.getResponseCode() == 304) {
    381                 // If isModifiedSince or If-None-Match has been set
    382                 // and the server answers with a HTTP 304 = "Not Modified"
    383                 Logging.debug("JCS - If-Modified-Since/ETag test: local version is up to date: {0}", getUrl());
    384                 // update cache attributes
    385                 attributes = parseHeaders(urlConn);
    386                 cache.put(getCacheKey(), cacheData, attributes);
    387                 return true;
    388             } else if (isObjectLoadable() // we have an object in cache, but we haven't received 304 response code
    389                     && (
    390                     (attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getHeaderField("ETag"))) ||
    391                             attributes.getLastModification() == urlConn.getLastModified())
    392             ) {
    393                 // we sent ETag or If-Modified-Since, but didn't get 304 response code
    394                 // for further requests - use HEAD
    395                 String serverKey = getServerKey();
    396                 Logging.info("JCS - Host: {0} found not to return 304 codes for If-Modified-Since or If-None-Match headers",
    397                         serverKey);
    398                 useHead.put(serverKey, Boolean.TRUE);
    399             }
    400 
    401             attributes = parseHeaders(urlConn);
    402 
    403             for (int i = 0; i < 5; ++i) {
    404                 if (urlConn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE) {
    405                     Thread.sleep(5000L+new SecureRandom().nextInt(5000));
    406                     continue;
    407                 }
    408 
    409                 attributes.setResponseCode(urlConn.getResponseCode());
    410                 byte[] raw;
    411                 if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
    412                     raw = Utils.readBytesFromStream(urlConn.getContent());
    413                 } else {
    414                     raw = new byte[]{};
    415                     try {
    416                         String data = urlConn.fetchContent();
    417                         if (!data.isEmpty()) {
    418                             String detectErrorMessage = detectErrorMessage(data);
    419                             if (detectErrorMessage != null) {
    420                                 attributes.setErrorMessage(detectErrorMessage);
    421                             }
    422                         }
    423                     } catch (IOException e) {
    424                         Logging.warn(e);
    425                     }
    426                 }
    427 
    428                 if (isResponseLoadable(urlConn.getHeaderFields(), urlConn.getResponseCode(), raw)) {
    429                     // we need to check cacheEmpty, so for cases, when data is returned, but we want to store
    430                     // as empty (eg. empty tile images) to save some space
    431                     cacheData = createCacheEntry(raw);
    432                     cache.put(getCacheKey(), cacheData, attributes);
    433                     Logging.debug("JCS - downloaded key: {0}, length: {1}, url: {2}",
    434                             getCacheKey(), raw.length, getUrl());
    435                     return true;
    436                 } else if (cacheAsEmpty(urlConn.getHeaderFields(), urlConn.getResponseCode())) {
    437                     cacheData = createCacheEntry(new byte[]{});
    438                     cache.put(getCacheKey(), cacheData, attributes);
    439                     Logging.debug("JCS - Caching empty object {0}", getUrl());
    440                     return true;
    441                 } else {
    442                     Logging.debug("JCS - failure during load - response is not loadable nor cached as empty");
    443                     return false;
    444                 }
    445             }
    446         } catch (FileNotFoundException e) {
    447             Logging.debug("JCS - Caching empty object as server returned 404 for: {0}", getUrlNoException());
    448             attributes.setResponseCode(404);
    449             attributes.setError(e);
    450             attributes.setException(e);
    451             boolean doCache = isResponseLoadable(null, 404, null) || cacheAsEmpty(Collections.emptyMap(), 404);
    452             if (doCache) {
    453                 cacheData = createCacheEntry(new byte[]{});
    454                 cache.put(getCacheKey(), cacheData, attributes);
    455             }
    456             return doCache;
    457         } catch (IOException e) {
    458             Logging.debug("JCS - IOException during communication with server for: {0}", getUrlNoException());
    459             if (isObjectLoadable()) {
    460                 return true;
    461             } else {
    462                 attributes.setError(e);
    463                 attributes.setException(e);
    464                 attributes.setResponseCode(599); // set dummy error code, greater than 500 so it will be not cached
    465                 return false;
    466             }
    467 
    468         } catch (InterruptedException e) {
    469             attributes.setError(e);
    470             attributes.setException(e);
    471             Logging.logWithStackTrace(Logging.LEVEL_WARN, e, "JCS - Exception during download {0}", getUrlNoException());
    472             Thread.currentThread().interrupt();
    473         }
    474         Logging.warn("JCS - Silent failure during download: {0}", getUrlNoException());
    475         return false;
    476     }
    477 
    478     /**
    479      * Tries do detect an error message from given string.
    480      * @param data string to analyze
    481      * @return error message if detected, or null
    482      * @since 14535
    483      */
    484     public String detectErrorMessage(String data) {
    485         Matcher m = HttpClient.getTomcatErrorMatcher(data);
    486         return m.matches() ? m.group(1).replace("'", "''") : null;
    487     }
    488 
    489     /**
    490      * Check if the object is loadable. This means, if the data will be parsed, and if this response
    491      * will finish as successful retrieve.
    492      *
    493      * This simple implementation doesn't load empty response, nor client (4xx) and server (5xx) errors
    494      *
    495      * @param headerFields headers sent by server
    496      * @param responseCode http status code
    497      * @param raw data read from server
    498      * @return true if object should be cached and returned to listener
    499      */
    500     protected boolean isResponseLoadable(Map<String, List<String>> headerFields, int responseCode, byte[] raw) {
    501         return raw != null && raw.length != 0 && responseCode < 400;
    502     }
    503 
    504     protected abstract V createCacheEntry(byte[] content);
    505 
    506     protected CacheEntryAttributes parseHeaders(HttpClient.Response urlConn) {
    507         CacheEntryAttributes ret = new CacheEntryAttributes();
    508 
    509         /*
    510          * according to https://www.ietf.org/rfc/rfc2616.txt Cache-Control takes precedence over max-age
    511          * max-age is for private caches, s-max-age is for shared caches. We take any value that is larger
    512          */
    513         Long expiration = 0L;
    514         String cacheControl = urlConn.getHeaderField("Cache-Control");
    515         if (cacheControl != null) {
    516             for (String token: cacheControl.split(",", -1)) {
    517                 try {
    518                     if (token.startsWith("max-age=")) {
    519                         expiration = Math.max(expiration,
    520                                 TimeUnit.SECONDS.toMillis(Long.parseLong(token.substring("max-age=".length())))
    521                                         + System.currentTimeMillis()
    522                         );
    523                     }
    524                     if (token.startsWith("s-max-age=")) {
    525                         expiration = Math.max(expiration,
    526                                 TimeUnit.SECONDS.toMillis(Long.parseLong(token.substring("s-max-age=".length())))
    527                                         + System.currentTimeMillis()
    528                         );
    529                     }
    530                 } catch (NumberFormatException e) {
    531                     // ignore malformed Cache-Control headers
    532                     Logging.trace(e);
    533                 }
    534             }
    535         }
    536 
    537         if (expiration.equals(0L)) {
    538             expiration = urlConn.getExpiration();
    539         }
    540 
    541         // if nothing is found - set default
    542         if (expiration.equals(0L)) {
    543             expiration = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
    544         }
    545 
    546         ret.setExpirationTime(Math.max(minimumExpiryTime + System.currentTimeMillis(), expiration));
    547         ret.setLastModification(now);
    548         ret.setEtag(urlConn.getHeaderField("ETag"));
    549 
    550         return ret;
    551     }
    552 
    553     private HttpClient getRequest(String requestMethod) throws IOException {
    554         final HttpClient urlConn = HttpClient.create(getUrl(), requestMethod);
    555         urlConn.setAccept("text/html, image/png, image/jpeg, image/gif, */*");
    556         urlConn.setReadTimeout(readTimeout); // 30 seconds read timeout
    557         urlConn.setConnectTimeout(connectTimeout);
    558         if (headers != null) {
    559             urlConn.setHeaders(headers);
    560         }
    561 
    562         final boolean noCache = force
    563                 // To remove when switching to Java 11
    564                 // Workaround for https://bugs.openjdk.java.net/browse/JDK-8146450
    565                 || (Utils.getJavaVersion() == 8 && Utils.isRunningJavaWebStart());
    566         urlConn.useCache(!noCache);
    567 
    568         return urlConn;
    569     }
    570 
    571     private boolean isCacheValidUsingHead() throws IOException {
    572         final HttpClient.Response urlConn = getRequest("HEAD").connect();
    573         long lastModified = urlConn.getLastModified();
    574         boolean ret = (attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getHeaderField("ETag"))) ||
    575                 (lastModified != 0 && lastModified <= attributes.getLastModification());
    576         if (ret) {
    577             // update attributes
    578             attributes = parseHeaders(urlConn);
    579             cache.put(getCacheKey(), cacheData, attributes);
    580         }
    581         return ret;
    582     }
    583 
    584     /**
    585      * TODO: move to JobFactory
    586      * cancels all outstanding tasks in the queue.
    587      */
    588     public void cancelOutstandingTasks() {
    589         for (Runnable r: downloadJobExecutor.getQueue()) {
    590             if (downloadJobExecutor.remove(r) && r instanceof org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob) {
    591                 ((org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob<?, ?>) r).handleJobCancellation();
    592             }
    593         }
    594     }
    595 
    596     /**
    597      * Sets a job, that will be run, when job will finish execution
    598      * @param runnable that will be executed
    599      */
    600     public void setFinishedTask(Runnable runnable) {
    601         this.finishTask = runnable;
    602 
    603     }
    604 
    605     /**
    606      * Marks this job as canceled
    607      */
    608     public void handleJobCancellation() {
    609         finishLoading(LoadResult.CANCELED);
    610     }
    611 
    612     private URL getUrlNoException() {
    613         try {
    614             return getUrl();
    615         } catch (IOException e) {
    616             Logging.trace(e);
    617             return null;
    618         }
    619     }
    620 }
  • applications/editors/josm/plugins/pmtiles/src/main/java/org/openstreetmap/josm/plugins/pmtiles/gui/layers/PMTileJob.java

    r36112 r36145  
    1919import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
    2020import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
     21import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
    2122import org.openstreetmap.josm.data.imagery.TileJobOptions;
    2223import org.openstreetmap.josm.plugins.pmtiles.lib.DirectoryCache;
Note: See TracChangeset for help on using the changeset viewer.