Ticket #11216: jcs_cache_v02.patch
File jcs_cache_v02.patch, 56.1 KB (added by , 9 years ago) |
---|
-
build.xml
220 220 destdir="build" target="1.7" source="1.7" debug="on" includeantruntime="false" createMissingPackageInfoClass="false" encoding="iso-8859-1"> 221 221 <!-- get rid of "internal proprietary API" warning --> 222 222 <compilerarg value="-XDignore.symbol.file"/> 223 <exclude name="org/apache/commons/jcs/admin/**"/> 224 <exclude name="org/apache/commons/jcs/auxiliary/disk/jdbc/**"/> 225 <exclude name="org/apache/commons/jcs/auxiliary/remote/**"/> 226 <exclude name="org/apache/commons/jcs/utils/servlet/**"/> 227 <exclude name="org/apache/commons/logging/impl/AvalonLogger.java"/> 228 <exclude name="org/apache/commons/logging/impl/Log4JLogger.java"/> 229 <exclude name="org/apache/commons/logging/impl/LogKitLogger.java"/> 230 <exclude name="org/apache/commons/logging/impl/ServletContextCleaner.java"/> 223 231 </javac> 224 232 <!-- JMapViewer/JOSM --> 225 233 <javac srcdir="${src.dir}" excludes="com/**,oauth/**,org/apache/commons/**,org/glassfish/**,org/openstreetmap/gui/jmapviewer/Demo.java" … … 581 589 </java> 582 590 </target> 583 591 </project> 592 -
src/org/apache/commons
-
src/org/openstreetmap/josm/Main.java
Property changes on: src/org/apache/commons ___________________________________________________________________ Modified: svn:externals ## -1 +1,3 ## -codec http://svn.apache.org/repos/asf/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec +http://svn.apache.org/repos/asf/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec codec +http://svn.apache.org/repos/asf/commons/proper/jcs/trunk/commons-jcs-core/src/main/java/org/apache/commons/jcs jcs +http://svn.apache.org/repos/asf/commons/proper/logging/trunk/src/main/java/org/apache/commons/logging logging
67 67 import org.openstreetmap.josm.data.ProjectionBounds; 68 68 import org.openstreetmap.josm.data.UndoRedoHandler; 69 69 import org.openstreetmap.josm.data.ViewportData; 70 import org.openstreetmap.josm.data.cache.JCSCacheManager; 70 71 import org.openstreetmap.josm.data.coor.CoordinateFormat; 71 72 import org.openstreetmap.josm.data.coor.LatLon; 72 73 import org.openstreetmap.josm.data.osm.DataSet; … … 1088 1089 * @since 3378 1089 1090 */ 1090 1091 public static boolean exitJosm(boolean exit, int exitCode) { 1092 JCSCacheManager.shutdown(); 1091 1093 if (Main.saveUnsavedModifications()) { 1092 1094 geometry.remember("gui.geometry"); 1093 1095 if (map != null) { -
src/org/openstreetmap/josm/data/cache/CacheEntry.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.io.Serializable; 5 6 /** 7 * @author Wiktor Niesiobędzki 8 * 9 * Class that will hold JCS cache entries 10 * 11 */ 12 public class CacheEntry implements Serializable { 13 private static final long serialVersionUID = 1L; //version 14 private byte[] content; 15 16 /** 17 * @param content of the cache entry 18 */ 19 public CacheEntry(byte[] content) { 20 this.content = content; 21 } 22 23 /** 24 * @return cache entry content 25 */ 26 public byte[] getContent() { 27 return content; 28 } 29 } -
src/org/openstreetmap/josm/data/cache/CacheEntryAttributes.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import org.apache.commons.jcs.engine.ElementAttributes; 8 9 public class CacheEntryAttributes extends ElementAttributes { 10 private static final long serialVersionUID = 1L; //version 11 private Map<String, String> attrs = new HashMap<String, String>(); 12 private final static String NO_TILE_AT_ZOOM = "noTileAtZoom"; 13 private final static String ETAG = "Etag"; 14 private final static String LAST_MODIFICATION = "lastModification"; 15 private final static String EXPIRATION_TIME = "expirationTime"; 16 //private boolean noTileAtZoom = false; 17 private String Etag = null; 18 private long lastModification = 0; 19 private long expirationTime = 0; 20 21 22 public CacheEntryAttributes() { 23 super(); 24 attrs.put(NO_TILE_AT_ZOOM, "false"); 25 attrs.put(ETAG, null); 26 attrs.put(LAST_MODIFICATION, "0"); 27 attrs.put(EXPIRATION_TIME, "0"); 28 } 29 public boolean isNoTileAtZoom() { 30 return Boolean.toString(true).equals(attrs.get(NO_TILE_AT_ZOOM)); 31 } 32 public void setNoTileAtZoom(boolean noTileAtZoom) { 33 attrs.put(NO_TILE_AT_ZOOM, Boolean.toString(noTileAtZoom)); 34 } 35 public String getEtag() { 36 return attrs.get(ETAG); 37 } 38 public void setEtag(String etag) { 39 attrs.put(ETAG, etag); 40 } 41 42 private long getLongAttr(String key) { 43 try { 44 return Long.parseLong(attrs.get(key)); 45 } catch (NumberFormatException e) { 46 attrs.put(key, "0"); 47 return 0; 48 } 49 } 50 51 public long getLastModification() { 52 return getLongAttr(LAST_MODIFICATION); 53 } 54 public void setLastModification(long lastModification) { 55 attrs.put(LAST_MODIFICATION, Long.toString(lastModification)); 56 } 57 public long getExpirationTime() { 58 return getLongAttr(EXPIRATION_TIME); 59 } 60 public void setExpirationTime(long expirationTime) { 61 attrs.put(EXPIRATION_TIME, Long.toString(expirationTime)); 62 } 63 64 } -
src/org/openstreetmap/josm/data/cache/ICachedLoaderJob.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.net.URL; 5 6 7 /** 8 * 9 * @author Wiktor Niesiobędzki 10 * 11 * @param <K> cache key type 12 * @param <V> value that is returned from cache 13 */ 14 public interface ICachedLoaderJob<K> { 15 /** 16 * returns cache entry key 17 * 18 * @param tile 19 * @return cache key for tile 20 */ 21 public K getCacheKey(); 22 23 /** 24 * method to get download URL for Job 25 * @return URL that should be fetched 26 * 27 */ 28 public URL getUrl(); 29 /** 30 * implements the main algorithm for fetching 31 */ 32 public void run(); 33 34 /** 35 * fetches object from cache, or returns null when object is not found 36 * 37 * @return filled tile with data or null when no cache entry found 38 */ 39 public byte[] get(); 40 41 /** 42 * Submit job for background fetch, and listener will be 43 * fed with value object 44 * 45 * @param listener 46 */ 47 public void submit(ICachedLoaderListener listener); 48 } -
src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 public interface ICachedLoaderListener { 5 /** 6 * Will be called when K object was successfully downloaded 7 * 8 * @param object 9 * @param success 10 */ 11 public void loadingFinished(byte[] object, boolean success); 12 13 } -
src/org/openstreetmap/josm/data/cache/JCSCacheManager.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.io.File; 5 import java.io.IOException; 6 import java.util.Properties; 7 import java.util.logging.Level; 8 import java.util.logging.Logger; 9 10 import org.apache.commons.jcs.access.CacheAccess; 11 import org.apache.commons.jcs.auxiliary.AuxiliaryCache; 12 import org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes; 13 import org.apache.commons.jcs.engine.control.CompositeCache; 14 import org.apache.commons.jcs.engine.control.CompositeCacheManager; 15 import org.openstreetmap.josm.Main; 16 17 18 /** 19 * @author Wiktor Niesiobędzki 20 * 21 * Wrapper class for JCS Cache. Sets some sane environment and returns instances of cache objects. 22 * Static configuration for now assumes some small LRU cache in memory and larger LRU cache on disk 23 * 24 */ 25 public class JCSCacheManager { 26 27 private static volatile CompositeCacheManager cacheManager = null; 28 private static long maxObjectTTL = Long.MAX_VALUE; 29 30 private static int maxObjectsInMemory = 1000; 31 private static int maxObjectsOnDisk = 25000; 32 33 private static void initialize() throws IOException { 34 File cacheDir = new File(Main.pref.getCacheDirectory(), "jcs"); 35 36 if ((!cacheDir.exists() && !cacheDir.mkdirs())) 37 throw new IOException("Cannot access cache directory"); 38 39 // raising logging level gives ~500x performance gain 40 // http://westsworld.dk/blog/2008/01/jcs-and-performance/ 41 Logger.getLogger("org.apache.commons.jcs").setLevel(Level.INFO); 42 43 CompositeCacheManager cm = CompositeCacheManager.getUnconfiguredInstance(); 44 // this could be moved to external file 45 Properties props = new Properties(); 46 props.setProperty("jcs.default", "DC"); 47 props.setProperty("jcs.default.cacheattributes", org.apache.commons.jcs.engine.CompositeCacheAttributes.class.getCanonicalName()); 48 props.setProperty("jcs.default.cacheattributes.MaxObjects", Long.toString(maxObjectsInMemory)); 49 props.setProperty("jcs.default.cacheattributes.UseMemoryShrinker", "true"); 50 props.setProperty("jcs.default.cacheattributes.DiskUsagePatternName", "UPDATE"); // store elements on disk on put 51 props.setProperty("jcs.default.elementattributes", CacheEntryAttributes.class.getCanonicalName()); 52 props.setProperty("jcs.default.elementattributes.IsEternal", "false"); 53 props.setProperty("jcs.default.elementattributes.MaxLife", Long.toString(maxObjectTTL)); 54 props.setProperty("jcs.default.elementattributes.IdleTime", Long.toString(maxObjectTTL)); 55 props.setProperty("jcs.default.elementattributes.IsSpool", "true"); 56 props.setProperty("jcs.auxiliary.DC", org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory.class.getCanonicalName()); 57 props.setProperty("jcs.auxiliary.DC.attributes", org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes.class.getCanonicalName()); 58 props.setProperty("jcs.auxiliary.DC.attributes.DiskPath", cacheDir.getAbsolutePath()); 59 props.setProperty("jcs.auxiliary.DC.attributes.maxKeySize", Long.toString(maxObjectsOnDisk)); 60 props.setProperty("jcs.auxiliary.DC.attributes.blockSizeBytes", "1024"); 61 cm.configure(props); 62 cacheManager = cm; 63 64 } 65 66 /** 67 * Returns configured cache object for named cache region 68 * @param cacheName region name 69 * @return cache access object 70 * @throws IOException if directory is not found 71 */ 72 public static <K,V> CacheAccess<K, V> getCache(String cacheName) throws IOException { 73 return getCache(cacheName, maxObjectsInMemory, maxObjectsOnDisk); 74 } 75 76 /** 77 * Returns configured cache object with defined limits of memory cache and disk cache 78 * @param cacheName region name 79 * @param maxMemoryObjects number of objects to keep in memory 80 * @param maxDiskObjects number of objects to keep on disk 81 * @return cache access object 82 * @throws IOException if directory is not found 83 */ 84 public static <K,V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects) throws IOException { 85 if (cacheManager != null) 86 return getCacheInner(cacheName, maxMemoryObjects, maxDiskObjects); 87 88 synchronized (JCSCacheManager.class) { 89 if (cacheManager == null) 90 initialize(); 91 return getCacheInner(cacheName, maxMemoryObjects, maxDiskObjects); 92 } 93 } 94 95 private static <K,V> CacheAccess<K, V> getCacheInner(String cacheName, int maxMemoryObjects, int maxDiskObjects) { 96 CompositeCache<K, V> cc = cacheManager.getCache(cacheName); 97 cc.getCacheAttributes().setMaxObjects(maxMemoryObjects); 98 AuxiliaryCache<K, V> ac[] = cc.getAuxCaches(); 99 if (ac!=null && ac.length > 0) { 100 if (ac[0].getAuxiliaryCacheAttributes() instanceof BlockDiskCacheAttributes) { 101 ((BlockDiskCacheAttributes) ac[0].getAuxiliaryCacheAttributes()).setMaxKeySize(maxDiskObjects); 102 } 103 } 104 return new CacheAccess<K, V>(cc); 105 } 106 107 public static void shutdown() { 108 // use volatile semantics to get consistent object 109 CompositeCacheManager localCacheManager = cacheManager; 110 if (localCacheManager != null) { 111 localCacheManager.shutDown(); 112 } 113 114 } 115 116 } -
src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.io.ByteArrayOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.net.HttpURLConnection; 8 import java.net.MalformedURLException; 9 import java.net.URL; 10 import java.net.URLConnection; 11 import java.util.HashSet; 12 import java.util.Map; 13 import java.util.Random; 14 import java.util.Set; 15 import java.util.concurrent.ConcurrentHashMap; 16 import java.util.concurrent.ConcurrentMap; 17 import java.util.logging.Level; 18 import java.util.logging.Logger; 19 20 import org.apache.commons.jcs.access.behavior.ICacheAccess; 21 import org.apache.commons.jcs.engine.behavior.ICacheElement; 22 import org.openstreetmap.gui.jmapviewer.FeatureAdapter; 23 24 /** 25 * @author Wiktor Niesiobędzki 26 * 27 * @param <K> cache entry key type 28 * 29 * Generic loader for HTTP based tiles. Uses custom attribute, to check, if entry has expired 30 * according to HTTP headers sent with tile. If so, it tries to verify using Etags 31 * or If-Modified-Since / Last-Modified. 32 * 33 * If the tile is not valid, it will try to download it from remote service and put it 34 * to cache. If remote server will fail it will try to use stale entry. 35 * 36 * This class will keep only one Job running for specified tile. All others will just finish, but 37 * listeners will be gathered and notified, once download job will be finished 38 */ 39 public abstract class JCSCachedTileLoaderJob<K> implements ICachedLoaderJob<K> { 40 private static final Logger log = FeatureAdapter.getLogger(JCSCachedTileLoaderJob.class.getCanonicalName()); 41 protected static final long DEFAULT_EXPIRE_TIME = 1000L * 60 * 60 * 24 * 7; // 7 days 42 // Limit for the max-age value send by the server. 43 protected static final long EXPIRE_TIME_SERVER_LIMIT = 1000L * 60 * 60 * 24 * 28; // 4 weeks 44 // Absolute expire time limit. Cached tiles that are older will not be used, 45 // even if the refresh from the server fails. 46 protected static final long ABSOLUTE_EXPIRE_TIME_LIMIT = Long.MAX_VALUE; // unlimited 47 48 private ICacheAccess<K, CacheEntry> cache; 49 private long now; 50 private ICacheElement<K,CacheEntry> cacheElement; 51 private int connectTimeout; 52 private int readTimeout; 53 private Map<String, String> headers; 54 private static ConcurrentMap<URL,Set<ICachedLoaderListener>> inProgress = new ConcurrentHashMap<>(); 55 56 protected CacheEntryAttributes attributes = null; 57 protected byte[] data = null; 58 private static ConcurrentMap<String, Boolean> useHead = new ConcurrentHashMap<>(); 59 60 61 62 /** 63 * @param cache cache instance that we will work on 64 * @param headers 65 * @param readTimeout 66 * @param connectTimeout 67 */ 68 public JCSCachedTileLoaderJob(ICacheAccess<K,CacheEntry> cache, 69 int connectTimeout, int readTimeout, 70 Map<String, String> headers) { 71 72 this.cache = cache; 73 this.now = System.currentTimeMillis(); 74 this.connectTimeout = connectTimeout; 75 this.readTimeout = readTimeout; 76 this.headers = headers; 77 } 78 79 private void ensureCacheElement() { 80 if (cacheElement == null && getCacheKey() != null) { 81 cacheElement = cache.getCacheElement(getCacheKey()); 82 if (cacheElement != null) { 83 attributes = (CacheEntryAttributes) cacheElement.getElementAttributes(); 84 data = cacheElement.getVal().getContent(); 85 } 86 } 87 } 88 89 public byte[] get() { 90 ensureCacheElement(); 91 if (cacheElement != null) { 92 return cacheElement.getVal().getContent(); 93 } 94 return null; 95 } 96 97 @Override 98 public void submit(ICachedLoaderListener listener) { 99 boolean first = false; 100 URL url = getUrl(); 101 if (url == null) { 102 log.log(Level.WARNING, "No url returned for: {0}, skipping", getCacheKey()); 103 return; 104 } 105 synchronized (inProgress) { 106 Set<ICachedLoaderListener> newListeners = inProgress.get(url); 107 if (newListeners == null) { 108 newListeners = new HashSet<>(); 109 inProgress.put(url, newListeners); 110 first = true; 111 } 112 newListeners.add(listener); 113 } 114 115 if (first) { 116 ensureCacheElement(); 117 if (cacheElement != null && isCacheElementValid() && (isObjectLoadable())) { 118 // we got something in cache, verify it 119 log.log(Level.FINE, "JCS - Returning object from cache: {0}", getCacheKey()); 120 finishLoading(true, true); 121 return; 122 } 123 // object not in cache, so submit work to separate thread 124 JCSJobDispatcher.getInstance().addJob(this); 125 } 126 127 } 128 129 /** 130 * 131 * @return checks if object from cache has sufficient data to be returned 132 */ 133 public boolean isObjectLoadable() { 134 return data != null && data.length > 0; 135 } 136 137 /** 138 * 139 * @return cache object as empty, regardless of what remote resource has returned (ex. based on headers) 140 */ 141 protected boolean cacheAsEmpty() { 142 return false; 143 } 144 145 public void run() { 146 // try to load object from remote resource 147 if (loadObject()) { 148 finishLoading(true, true); 149 } else { 150 // if loading failed - check if we can return stale entry 151 if (isObjectLoadable()) { 152 // try to get stale entry in cache 153 finishLoading(true, true); 154 log.log(Level.FINE, "JCS - found stale object in cache: {0}", getUrl()); 155 } else { 156 // failed completely 157 finishLoading(false, true); 158 } 159 } 160 } 161 162 163 private void finishLoading(boolean success, boolean loaded) { 164 Set<ICachedLoaderListener> listeners = null; 165 synchronized (inProgress) { 166 listeners = inProgress.remove(getUrl()); 167 } 168 if (listeners == null) { 169 log.log(Level.WARNING, "Listener not found for URL: {0}. Listener not notified!", getUrl()); 170 return; 171 } 172 try { 173 for (ICachedLoaderListener l: listeners) { 174 l.loadingFinished(data, success); 175 } 176 } catch (Exception e) { 177 log.log(Level.WARNING, "JCS TMS - Error while loading image from tile cache: {0}; {1}", new Object[]{e.getMessage(), getUrl()}); 178 log.log(Level.FINE, "Stacktrace", e); 179 for (ICachedLoaderListener l: listeners) { 180 l.loadingFinished(data, false); 181 } 182 183 } 184 185 } 186 187 private boolean isCacheElementValid() { 188 long expires = attributes.getExpirationTime(); 189 190 // check by expire date set by server 191 if (expires != 0L) { 192 // put a limit to the expire time (some servers send a value 193 // that is too large) 194 expires = Math.min(expires, attributes.getCreateTime() + EXPIRE_TIME_SERVER_LIMIT); 195 if (now > expires) { 196 log.log(Level.FINE, "TMS - Tile has expired ({0})-> not valid {1}", new Object[]{Long.toString(expires), getUrl()}); 197 return false; 198 } 199 } else { 200 // check by file modification date 201 if (now - attributes.getLastModification() > DEFAULT_EXPIRE_TIME) { 202 log.log(Level.FINE, "TMS - Tile has expired, maximum file age reached {0}", getUrl()); 203 return false; 204 } 205 } 206 return true; 207 } 208 209 private boolean loadObject() { 210 try { 211 // if we have object in cache, and host doesn't support If-Modified-Since nor If-None-Match 212 // then just use HEAD request and check returned values 213 if (isObjectLoadable() && useHead.get(getUrl().getHost()) && isCacheValidUsingHead()) { 214 log.log(Level.FINE, "JCS - cache entry verified using HEAD request: {0}", getUrl()); 215 return true; 216 } 217 URLConnection urlConn = getURLConnection(); 218 219 if (isObjectLoadable() && 220 (now - attributes.getLastModification()) <= ABSOLUTE_EXPIRE_TIME_LIMIT) { 221 urlConn.setIfModifiedSince(attributes.getLastModification()); 222 } 223 if (isObjectLoadable() && attributes.getEtag() != null) { 224 urlConn.addRequestProperty("If-None-Match", attributes.getEtag()); 225 } 226 if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) { 227 // If isModifiedSince or If-None-Match has been set 228 // and the server answers with a HTTP 304 = "Not Modified" 229 log.log(Level.FINE, "JCS - IfModifiedSince/Etag test: local version is up to date: {0}", getUrl()); 230 return true; 231 } else if (isObjectLoadable()) { 232 // we have an object in cache, but we haven't received 304 resposne code 233 // check if we should use HEAD request to verify 234 if((attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getRequestProperty("ETag"))) || 235 attributes.getLastModification() == urlConn.getLastModified()) { 236 // we sent ETag or If-Modified-Since, but didn't get 304 response code 237 // for further requests - use HEAD 238 useHead.put(getUrl().getHost(), Boolean.TRUE); 239 } 240 } 241 242 attributes = parseHeaders(urlConn); 243 244 for (int i = 0; i < 5; ++i) { 245 if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) { 246 Thread.sleep(5000+(new Random()).nextInt(5000)); 247 continue; 248 } 249 data = read(urlConn); 250 if (!cacheAsEmpty() && data != null && data.length > 0) { 251 cache.put(getCacheKey(), new CacheEntry(data), attributes); 252 log.log(Level.FINE, "JCS - downloaded key: {0}, length: {1}, url: {2}", 253 new Object[] {getCacheKey(), data.length, getUrl()}); 254 return true; 255 } else { 256 log.log(Level.FINE, "JCS - Caching empty object {0}", getUrl()); 257 cache.put(getCacheKey(), new CacheEntry(new byte[]{}), attributes); 258 return true; 259 } 260 } 261 } catch (Exception e) { 262 log.log(Level.WARNING, "JCS - Exception during download {0}: {1}", new Object[]{getUrl(), e.getMessage() == null ? e.toString() : e.getMessage() }); 263 } 264 log.log(Level.WARNING, "JCS - Silent failure during download: {0}", getUrl()); 265 return false; 266 267 } 268 269 private CacheEntryAttributes parseHeaders(URLConnection urlConn) { 270 CacheEntryAttributes ret = new CacheEntryAttributes(); 271 ret.setNoTileAtZoom("no-tile".equals(urlConn.getHeaderField("X-VE-Tile-Info"))); 272 273 Long lng = urlConn.getExpiration(); 274 if (lng.equals(0L)) { 275 try { 276 String str = urlConn.getHeaderField("Cache-Control"); 277 if (str != null) { 278 for (String token: str.split(",")) { 279 if (token.startsWith("max-age=")) { 280 lng = Long.parseLong(token.substring(8)) * 1000 + 281 System.currentTimeMillis(); 282 } 283 } 284 } 285 } catch (NumberFormatException e) {} //ignore malformed Cache-Control headers 286 } 287 288 ret.setExpirationTime(lng); 289 ret.setLastModification(now); 290 ret.setEtag(urlConn.getHeaderField("ETag")); 291 return ret; 292 } 293 294 private HttpURLConnection getURLConnection() throws IOException, MalformedURLException { 295 HttpURLConnection urlConn = (HttpURLConnection) getUrl().openConnection(); 296 urlConn.setRequestProperty("Accept", "text/html, image/png, image/jpeg, image/gif, */*"); 297 urlConn.setReadTimeout(readTimeout); // 30 seconds read timeout 298 urlConn.setConnectTimeout(connectTimeout); 299 for(Map.Entry<String, String> e: headers.entrySet()) { 300 urlConn.setRequestProperty(e.getKey(), e.getValue()); 301 } 302 return urlConn; 303 } 304 305 private boolean isCacheValidUsingHead() throws IOException { 306 HttpURLConnection urlConn = (HttpURLConnection) getUrl().openConnection(); 307 urlConn.setRequestMethod("HEAD"); 308 long lastModified = urlConn.getLastModified(); 309 return ( 310 (attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getRequestProperty("ETag"))) || 311 (lastModified != 0 && lastModified <= attributes.getLastModification()) 312 ); 313 } 314 315 private static byte[] read(URLConnection urlConn) throws IOException { 316 InputStream input = urlConn.getInputStream(); 317 try { 318 ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available()); 319 byte[] buffer = new byte[2048]; 320 boolean finished = false; 321 do { 322 int read = input.read(buffer); 323 if (read >= 0) { 324 bout.write(buffer, 0, read); 325 } else { 326 finished = true; 327 } 328 } while (!finished); 329 if (bout.size() == 0) 330 return null; 331 return bout.toByteArray(); 332 } finally { 333 input.close(); 334 } 335 } 336 } -
src/org/openstreetmap/josm/data/cache/JCSJobDispatcher.java
1 // License: GPL. For details, see Readme.txt file. 2 package org.openstreetmap.josm.data.cache; 3 4 import java.util.concurrent.BlockingDeque; 5 import java.util.concurrent.LinkedBlockingDeque; 6 import java.util.concurrent.TimeUnit; 7 8 import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 9 10 /** 11 * A generic class that processes a list of {@link Runnable} one-by-one using 12 * one or more {@link Thread}-instances. The number of instances varies between 13 * 1 and {@link #workerThreadMaxCount} (default: 8). If an instance is idle 14 * more than {@link #workerThreadTimeout} seconds (default: 30), the instance 15 * ends itself. 16 * 17 * @author Jan Peter Stotz 18 */ 19 public class JCSJobDispatcher { 20 21 private static final JCSJobDispatcher instance = new JCSJobDispatcher(); 22 23 /** 24 * @return the singelton instance of the {@link JCSJobDispatcher} 25 */ 26 public static JCSJobDispatcher getInstance() { 27 return instance; 28 } 29 30 private JCSJobDispatcher() { 31 addWorkerThread().firstThread = true; 32 } 33 34 protected BlockingDeque<ICachedLoaderJob> jobQueue = new LinkedBlockingDeque<>(); 35 36 protected static int workerThreadMaxCount = 8; 37 38 /** 39 * Specifies the time span in seconds that a worker thread waits for new 40 * jobs to perform. If the time span has elapsed the worker thread 41 * terminates itself. Only the first worker thread works differently, it 42 * ignores the timeout and will never terminate itself. 43 */ 44 protected static int workerThreadTimeout = 30; 45 46 /** 47 * Type of queue, FIFO if <code>false</code>, LIFO if <code>true</code> 48 */ 49 protected boolean modeLIFO = false; 50 51 /** 52 * Total number of worker threads currently idle or active 53 */ 54 protected int workerThreadCount = 0; 55 56 /** 57 * Number of worker threads currently idle 58 */ 59 protected int workerThreadIdleCount = 0; 60 61 /** 62 * Just an id for identifying an worker thread instance 63 */ 64 protected int workerThreadId = 0; 65 66 /** 67 * Removes all jobs from the queue that are currently not being processed. 68 */ 69 public void cancelOutstandingJobs() { 70 jobQueue.clear(); 71 } 72 73 /** 74 * Function to set the maximum number of workers for tile loading. 75 */ 76 static public void setMaxWorkers(int workers) { 77 workerThreadMaxCount = workers; 78 } 79 80 /** 81 * Function to set the LIFO/FIFO mode for tile loading job. 82 * 83 * @param lifo <code>true</code> for LIFO mode, <code>false</code> for FIFO mode 84 */ 85 public void setLIFO(boolean lifo) { 86 modeLIFO = lifo; 87 } 88 89 /** 90 * Adds a job to the queue. 91 * Jobs for tiles already contained in the are ignored (using a <code>null</code> tile 92 * prevents skipping). 93 * 94 * @param job the the job to be added 95 */ 96 public void addJob(ICachedLoaderJob job) { 97 try { 98 if(job.getCacheKey() != null) { 99 for(ICachedLoaderJob oldJob : jobQueue) { 100 if(oldJob.getCacheKey() == job.getCacheKey()) { 101 return; 102 } 103 } 104 } 105 jobQueue.put(job); 106 if (workerThreadIdleCount == 0 && workerThreadCount < workerThreadMaxCount) 107 addWorkerThread(); 108 } catch (InterruptedException e) { 109 } 110 } 111 112 protected JobThread addWorkerThread() { 113 JobThread jobThread = new JobThread(++workerThreadId); 114 synchronized (this) { 115 workerThreadCount++; 116 } 117 jobThread.start(); 118 return jobThread; 119 } 120 121 public class JobThread extends Thread { 122 123 ICachedLoaderJob job; 124 boolean firstThread = false; 125 126 public JobThread(int threadId) { 127 super("OSMJobThread " + threadId); 128 setDaemon(true); 129 job = null; 130 } 131 132 @Override 133 public void run() { 134 executeJobs(); 135 synchronized (instance) { 136 workerThreadCount--; 137 } 138 } 139 140 protected void executeJobs() { 141 while (!isInterrupted()) { 142 try { 143 synchronized (instance) { 144 workerThreadIdleCount++; 145 } 146 if(modeLIFO) { 147 if (firstThread) 148 job = jobQueue.takeLast(); 149 else 150 job = jobQueue.pollLast(workerThreadTimeout, TimeUnit.SECONDS); 151 } else { 152 if (firstThread) 153 job = jobQueue.take(); 154 else 155 job = jobQueue.poll(workerThreadTimeout, TimeUnit.SECONDS); 156 } 157 } catch (InterruptedException e1) { 158 return; 159 } finally { 160 synchronized (instance) { 161 workerThreadIdleCount--; 162 } 163 } 164 if (job == null) 165 return; 166 try { 167 job.run(); 168 job = null; 169 } catch (Exception e) { 170 e.printStackTrace(); 171 } 172 } 173 } 174 } 175 176 } -
src/org/openstreetmap/josm/data/imagery/TMSCachedTileJob.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery; 3 4 import java.io.ByteArrayInputStream; 5 import java.io.IOException; 6 import java.net.URL; 7 import java.util.Map; 8 import java.util.logging.Level; 9 import java.util.logging.Logger; 10 11 import org.apache.commons.jcs.access.behavior.ICacheAccess; 12 import org.openstreetmap.gui.jmapviewer.FeatureAdapter; 13 import org.openstreetmap.gui.jmapviewer.Tile; 14 import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 15 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 16 import org.openstreetmap.josm.data.cache.CacheEntry; 17 import org.openstreetmap.josm.data.cache.ICachedLoaderListener; 18 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob; 19 20 /** 21 * @author Wiktor Niesiobędzki 22 * 23 * Class bridging TMS requests to JCS cache requests 24 * 25 */ 26 public class TMSCachedTileJob extends JCSCachedTileLoaderJob<String> implements TileJob, ICachedLoaderListener { 27 private static final Logger log = FeatureAdapter.getLogger(TMSCachedTileJob.class.getCanonicalName()); 28 private Tile tile; 29 private TileLoaderListener listener; 30 private URL url; 31 32 /** 33 * Constructor for creating a job, to get a specific tile from cache 34 * @param listener 35 * @param tile to be fetched from cache 36 * @param cache object 37 * @param connectTimeout when connecting to remote resource 38 * @param readTimeout when connecting to remote resource 39 * @param headers to be sent together with request 40 */ 41 public TMSCachedTileJob(TileLoaderListener listener, Tile tile, ICacheAccess<String, CacheEntry> cache, int connectTimeout, int readTimeout, 42 Map<String, String> headers) { 43 super(cache, connectTimeout, readTimeout, headers); 44 this.tile = tile; 45 this.listener = listener; 46 // URLs tend to change for some tile providers. Make a static reference here, so the tile URL might be used as a key 47 // for request deduplication 48 try { 49 this.url = new URL(tile.getUrl()); 50 } catch (IOException e) { 51 log.log(Level.WARNING, "JCS TMS Cache - error creating URL for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()}); 52 } 53 54 } 55 56 @Override 57 public Tile getTile() { 58 return tile; 59 } 60 61 @Override 62 public String getCacheKey() { 63 if (tile != null) 64 return tile.getKey(); 65 return null; 66 } 67 68 @Override 69 public URL getUrl() { 70 return url; 71 } 72 73 @Override 74 public boolean isObjectLoadable() { 75 return (data != null && data.length > 0) || cacheAsEmpty(); 76 } 77 78 @Override 79 protected boolean cacheAsEmpty() { 80 if (attributes != null && attributes.isNoTileAtZoom()) { 81 // do not remove file - keep the information, that there is no tile, for further requests 82 // the code above will check, if this information is still valid 83 log.log(Level.FINE, "JCS TMS - Tile valid, but no file, as no tiles at this level {0}", tile); 84 tile.setError("No tile at this zoom level"); 85 tile.putValue("tile-info", "no-tile"); 86 return true; 87 } 88 return false; 89 } 90 91 public void submit() { 92 super.submit(this); 93 } 94 95 @Override 96 public void loadingFinished(byte[] object, boolean success) { 97 try { 98 if (object != null && object.length > 0) { 99 tile.loadImage(new ByteArrayInputStream(object)); 100 } 101 tile.setLoaded(true); 102 if (listener != null) { 103 listener.tileLoadingFinished(tile, success); 104 } 105 } catch (IOException e) { 106 log.log(Level.WARNING, "JCS TMS - error loading object for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()}); 107 tile.setError(e.getMessage()); 108 tile.setLoaded(false); 109 if (listener != null) { 110 listener.tileLoadingFinished(tile, false); 111 } 112 } 113 } 114 115 /** 116 * Method for getting the tile from cache only, without trying to reach remote resource 117 * @return tile or null, if nothing (useful) was found in cache 118 */ 119 public Tile getCachedTile() { 120 byte[] data = super.get(); 121 if (isObjectLoadable()) { 122 loadingFinished(data, true); 123 return tile; 124 } else { 125 return null; 126 } 127 } 128 } 129 No newline at end of file -
src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.imagery; 3 4 import java.io.IOException; 5 import java.util.Map; 6 7 import org.apache.commons.jcs.access.behavior.ICacheAccess; 8 import org.openstreetmap.gui.jmapviewer.Tile; 9 import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 10 import org.openstreetmap.gui.jmapviewer.interfaces.TileJob; 11 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 12 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 13 import org.openstreetmap.josm.data.cache.CacheEntry; 14 import org.openstreetmap.josm.data.cache.JCSCacheManager; 15 import org.openstreetmap.josm.data.preferences.IntegerProperty; 16 17 /** 18 * @author Wiktor Niesiobędzki 19 * 20 * Wrapper class that bridges between JCS cache and Tile Loaders 21 * 22 */ 23 public class TMSCachedTileLoader implements TileLoader, CachedTileLoader { 24 25 private ICacheAccess<String, CacheEntry> cache; 26 private int connectTimeout; 27 private int readTimeout; 28 private Map<String, String> headers; 29 private TileLoaderListener listener; 30 public static final String PREFERENCE_PREFIX = "imagery.tms.cache."; 31 // average tile size is about 20kb 32 public static IntegerProperty MAX_OBJECTS_IN_MEMORY = new IntegerProperty(PREFERENCE_PREFIX + "max_objects_memory", 1000); // 1000 is around 20MB under this assumptions 33 public static IntegerProperty MAX_OBJECTS_ON_DISK = new IntegerProperty(PREFERENCE_PREFIX + "max_objects_disk", 25000); // 25000 is around 500MB under this assumptions 34 35 /** 36 * Constructor 37 * @param name of the cache 38 * @param connectTimeout to remote resource 39 * @param readTimeout to remote resource 40 * @param headers to be sent along with request 41 * @throws IOException when cache initialization fails 42 */ 43 public TMSCachedTileLoader(TileLoaderListener listener, String name, int connectTimeout, int readTimeout, Map<String, String> headers) throws IOException { 44 this.cache = JCSCacheManager.getCache(name, MAX_OBJECTS_IN_MEMORY.get(), MAX_OBJECTS_ON_DISK.get()); 45 this.connectTimeout = connectTimeout; 46 this.readTimeout = readTimeout; 47 this.headers = headers; 48 this.listener = listener; 49 } 50 51 @Override 52 public TileJob createTileLoaderJob(Tile tile) { 53 return new TMSCachedTileJob(listener, tile, cache, connectTimeout, readTimeout, headers); 54 } 55 56 @Override 57 public void clearCache() { 58 this.cache.clear(); 59 } 60 } -
src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
25 25 import org.openstreetmap.gui.jmapviewer.MemoryTileCache; 26 26 import org.openstreetmap.gui.jmapviewer.OsmTileLoader; 27 27 import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker; 28 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 28 29 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 29 30 import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOpenAerialTileSource; 30 31 import org.openstreetmap.gui.jmapviewer.tilesources.MapQuestOsmTileSource; … … 113 114 private static final StringProperty PROP_MAPSTYLE = new StringProperty("slippy_map_chooser.mapstyle", "Mapnik"); 114 115 public static final String RESIZE_PROP = SlippyMapBBoxChooser.class.getName() + ".resize"; 115 116 116 private OsmTileLoader cachedLoader;117 private TileLoader cachedLoader; 117 118 private OsmTileLoader uncachedLoader; 118 119 119 120 private final SizeButton iSizeButton; -
src/org/openstreetmap/josm/gui/layer/TMSLayer.java
21 21 import java.net.URL; 22 22 import java.util.ArrayList; 23 23 import java.util.Collections; 24 import java.util.Hash Set;24 import java.util.HashMap; 25 25 import java.util.LinkedList; 26 26 import java.util.List; 27 27 import java.util.Map; 28 import java.util.Map.Entry;29 28 import java.util.Scanner; 30 import java.util.Set;31 29 import java.util.concurrent.Callable; 32 30 import java.util.regex.Matcher; 33 31 import java.util.regex.Pattern; … … 43 41 import org.openstreetmap.gui.jmapviewer.Coordinate; 44 42 import org.openstreetmap.gui.jmapviewer.JobDispatcher; 45 43 import org.openstreetmap.gui.jmapviewer.MemoryTileCache; 46 import org.openstreetmap.gui.jmapviewer.OsmFileCacheTileLoader;47 44 import org.openstreetmap.gui.jmapviewer.OsmTileLoader; 48 import org.openstreetmap.gui.jmapviewer.TMSFileCacheTileLoader;49 45 import org.openstreetmap.gui.jmapviewer.Tile; 50 46 import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 51 import org.openstreetmap.gui.jmapviewer.interfaces.Tile ClearController;47 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 52 48 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 53 49 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 54 50 import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource; … … 63 59 import org.openstreetmap.josm.data.coor.LatLon; 64 60 import org.openstreetmap.josm.data.imagery.ImageryInfo; 65 61 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 62 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 66 63 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 67 64 import org.openstreetmap.josm.data.preferences.BooleanProperty; 68 65 import org.openstreetmap.josm.data.preferences.IntegerProperty; … … 75 72 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 76 73 import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 77 74 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 78 import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;79 75 import org.openstreetmap.josm.io.CacheCustomContent; 80 76 import org.openstreetmap.josm.io.OsmTransferException; 81 77 import org.openstreetmap.josm.io.UTFInputStreamReader; … … 122 118 } 123 119 124 120 public interface TileLoaderFactory { 125 OsmTileLoader makeTileLoader(TileLoaderListener listener); 121 TileLoader makeTileLoader(TileLoaderListener listener); 122 TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers); 126 123 } 127 124 125 // MemoryTileCache caches rendered tiles, to reduce latency during map panning 126 // ImageIO.read() takes a lot of time, so we can't use JCS cache 128 127 protected MemoryTileCache tileCache; 129 128 protected TileSource tileSource; 130 protected OsmTileLoader tileLoader;129 protected TileLoader tileLoader; 131 130 131 132 132 public static TileLoaderFactory loaderFactory = new TileLoaderFactory() { 133 133 @Override 134 public OsmTileLoader makeTileLoader(TileLoaderListener listener) { 135 String cachePath = TMSLayer.PROP_TILECACHE_DIR.get(); 136 if (cachePath != null && !cachePath.isEmpty()) { 137 try { 138 OsmFileCacheTileLoader loader; 139 loader = new TMSFileCacheTileLoader(listener, new File(cachePath)); 140 loader.headers.put("User-Agent", Version.getInstance().getFullAgentString()); 141 return loader; 142 } catch (IOException e) { 143 Main.warn(e); 144 } 134 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) { 135 Map<String, String> headers = new HashMap<>(); 136 headers.put("User-Agent", Version.getInstance().getFullAgentString()); 137 if (inputHeaders != null) 138 headers.putAll(inputHeaders); 139 140 try { 141 return new TMSCachedTileLoader(listener, "TMS", 142 Main.pref.getInteger("socket.timeout.connect",15) * 1000, 143 Main.pref.getInteger("socket.timeout.read", 30) * 1000, 144 headers); 145 } catch (IOException e) { 146 Main.warn(e); 145 147 } 146 148 return null; 147 149 } 150 151 @Override 152 public TileLoader makeTileLoader(TileLoaderListener listener) { 153 return makeTileLoader(listener, null); 154 } 148 155 }; 149 156 150 157 /** 151 158 * Plugins that wish to set custom tile loader should call this method 152 159 */ 160 153 161 public static void setCustomTileLoaderFactory(TileLoaderFactory loaderFactory) { 154 162 TMSLayer.loaderFactory = loaderFactory; 155 163 } 156 164 157 private Set<Tile> tileRequestsOutstanding = new HashSet<>();158 159 165 @Override 160 166 public synchronized void tileLoadingFinished(Tile tile, boolean success) { 161 167 if (tile.hasError()) { … … 170 176 if (Main.map != null) { 171 177 Main.map.repaint(100); 172 178 } 173 tileRequestsOutstanding.remove(tile);174 179 if (Main.isDebugEnabled()) { 175 180 Main.debug("tileLoadingFinished() tile: " + tile + " success: " + success); 176 181 } 177 182 } 178 183 179 private static class TmsTileClearController implements TileClearController, CancelListener {180 181 private final ProgressMonitor monitor;182 private boolean cancel = false;183 184 public TmsTileClearController(ProgressMonitor monitor) {185 this.monitor = monitor;186 this.monitor.addCancelListener(this);187 }188 189 @Override190 public void initClearDir(File dir) {191 }192 193 @Override194 public void initClearFiles(File[] files) {195 monitor.setTicksCount(files.length);196 monitor.setTicks(0);197 }198 199 @Override200 public boolean cancel() {201 return cancel;202 }203 204 @Override205 public void fileDeleted(File file) {206 monitor.setTicks(monitor.getTicks()+1);207 }208 209 @Override210 public void clearFinished() {211 monitor.finishTask();212 }213 214 @Override215 public void operationCanceled() {216 cancel = true;217 }218 }219 220 184 /** 221 185 * Clears the tile cache. 222 186 * … … 231 195 void clearTileCache(ProgressMonitor monitor) { 232 196 tileCache.clear(); 233 197 if (tileLoader instanceof CachedTileLoader) { 234 ((CachedTileLoader)tileLoader).clearCache( tileSource, new TmsTileClearController(monitor));198 ((CachedTileLoader)tileLoader).clearCache(); 235 199 } 236 200 } 237 201 … … 412 376 private void initTileSource(TileSource tileSource) { 413 377 this.tileSource = tileSource; 414 378 attribution.initialize(tileSource); 415 416 379 currentZoomLevel = getBestZoom(); 417 380 381 Map<String, String> headers = null; 382 if (tileSource instanceof TemplatedTMSTileSource) { 383 headers = (((TemplatedTMSTileSource)tileSource).getHeaders()); 384 } 385 418 386 tileCache = new MemoryTileCache(); 419 420 tileLoader = loaderFactory.makeTileLoader(this); 421 if (tileLoader == null) { 387 tileLoader = loaderFactory.makeTileLoader(this, headers); 388 if (tileLoader == null) 422 389 tileLoader = new OsmTileLoader(this); 423 }424 tileLoader.timeoutConnect = Main.pref.getInteger("socket.timeout.connect",15) * 1000;425 tileLoader.timeoutRead = Main.pref.getInteger("socket.timeout.read", 30) * 1000;426 if (tileSource instanceof TemplatedTMSTileSource) {427 for(Entry<String, String> e : ((TemplatedTMSTileSource)tileSource).getHeaders().entrySet()) {428 tileLoader.headers.put(e.getKey(), e.getValue());429 }430 }431 tileLoader.headers.put("User-Agent", Version.getInstance().getFullAgentString());432 390 } 433 391 434 392 @Override … … 487 445 setMaxWorkers(); 488 446 if(!isProjectionSupported(Main.getProjection())) { 489 447 JOptionPane.showMessageDialog(Main.parent, 490 tr("TMS layers do not support the projection {0}.\n{1}\n"491 + "Change the projection or remove the layer.",492 Main.getProjection().toCode(), nameSupportedProjections()),493 tr("Warning"),494 JOptionPane.WARNING_MESSAGE);448 tr("TMS layers do not support the projection {0}.\n{1}\n" 449 + "Change the projection or remove the layer.", 450 Main.getProjection().toCode(), nameSupportedProjections()), 451 tr("Warning"), 452 JOptionPane.WARNING_MESSAGE); 495 453 } 496 454 497 455 setBackgroundLayer(true); … … 629 587 new PleaseWaitRunnable(tr("Flush Tile Cache")) { 630 588 @Override 631 589 protected void realRun() throws SAXException, IOException, 632 590 OsmTransferException { 633 591 clearTileCache(getProgressMonitor()); 634 592 } 635 593 … … 685 643 } 686 644 needRedraw = true; 687 645 JobDispatcher.getInstance().cancelOutstandingJobs(); 688 tileRequestsOutstanding.clear();689 646 } 690 647 691 648 int getMaxZoomLvl() { … … 770 727 * are temporary only and intentionally not inserted 771 728 * into the tileCache. 772 729 */ 773 synchronizedTile tempCornerTile(Tile t) {730 Tile tempCornerTile(Tile t) { 774 731 int x = t.getXtile() + 1; 775 732 int y = t.getYtile() + 1; 776 733 int zoom = t.getZoom(); … … 780 737 return new Tile(tileSource, x, y, zoom); 781 738 } 782 739 783 synchronizedTile getOrCreateTile(int x, int y, int zoom) {740 Tile getOrCreateTile(int x, int y, int zoom) { 784 741 Tile tile = getTile(x, y, zoom); 785 742 if (tile == null) { 786 743 tile = new Tile(tileSource, x, y, zoom); … … 794 751 * This can and will return null for tiles that are not 795 752 * already in the cache. 796 753 */ 797 synchronizedTile getTile(int x, int y, int zoom) {754 Tile getTile(int x, int y, int zoom) { 798 755 int max = (1 << zoom); 799 756 if (x < 0 || x >= max || y < 0 || y >= max) 800 757 return null; … … 801 758 return tileCache.getTile(tileSource, x, y, zoom); 802 759 } 803 760 804 synchronizedboolean loadTile(Tile tile, boolean force) {761 boolean loadTile(Tile tile, boolean force) { 805 762 if (tile == null) 806 763 return false; 807 if (!force && (tile. hasError() || tile.isLoaded()))764 if (!force && (tile.isLoaded() || tile.hasError())) 808 765 return false; 809 766 if (tile.isLoading()) 810 767 return false; 811 if (tileRequestsOutstanding.contains(tile)) 812 return false; 813 tileRequestsOutstanding.add(tile); 814 JobDispatcher.getInstance().addJob(tileLoader.createTileLoaderJob(tile)); 768 tileLoader.createTileLoaderJob(tile).submit(); 815 769 return true; 816 770 } 817 771 … … 1268 1222 public TileSet getTileSet(int zoom) { 1269 1223 if (zoom < minZoom) 1270 1224 return nullTileSet; 1271 TileSet ts = tileSets[zoom-minZoom]; 1272 if (ts == null) { 1273 ts = new TileSet(topLeft, botRight, zoom); 1274 tileSets[zoom-minZoom] = ts; 1225 synchronized (tileSets) { 1226 TileSet ts = tileSets[zoom-minZoom]; 1227 if (ts == null) { 1228 ts = new TileSet(topLeft, botRight, zoom); 1229 tileSets[zoom-minZoom] = ts; 1230 } 1231 return ts; 1275 1232 } 1276 return ts;1277 1233 } 1234 1278 1235 public TileSetInfo getTileSetInfo(int zoom) { 1279 1236 if (zoom < minZoom) 1280 1237 return new TileSetInfo(); 1281 TileSetInfo tsi = tileSetInfos[zoom-minZoom]; 1282 if (tsi == null) { 1283 tsi = TMSLayer.getTileSetInfo(getTileSet(zoom)); 1284 tileSetInfos[zoom-minZoom] = tsi; 1238 synchronized (tileSetInfos) { 1239 TileSetInfo tsi = tileSetInfos[zoom-minZoom]; 1240 if (tsi == null) { 1241 tsi = TMSLayer.getTileSetInfo(getTileSet(zoom)); 1242 tileSetInfos[zoom-minZoom] = tsi; 1243 } 1244 return tsi; 1285 1245 } 1286 return tsi;1287 1246 } 1288 1247 } 1289 1248 1290 1249 @Override 1291 1250 public void paint(Graphics2D g, MapView mv, Bounds bounds) { 1292 //long start = System.currentTimeMillis();1293 1251 EastNorth topLeft = mv.getEastNorth(0, 0); 1294 1252 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight()); 1295 1253