Index: trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java	(revision 8647)
+++ trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java	(revision 8649)
@@ -174,11 +174,5 @@
         if (first || force) {
             ensureCacheElement();
-            if (!force && cacheElement != null && isCacheElementValid() && isObjectLoadable()) {
-                // we got something in cache, and it's valid, so lets return it
-                log.log(Level.FINE, "JCS - Returning object from cache: {0}", getCacheKey());
-                finishLoading(LoadResult.SUCCESS);
-                return;
-            }
-            // object not in cache, so submit work to separate thread
+            // submit all jobs to separate thread, so calling thread is not blocked with IO when loading from disk
             downloadJobExecutor.execute(this);
         }
@@ -229,4 +223,12 @@
         currentThread.setName("JCS Downloading: " + getUrl());
         try {
+            // try to fetch from cache
+            if (!force && cacheElement != null && isCacheElementValid() && isObjectLoadable()) {
+                // we got something in cache, and it's valid, so lets return it
+                log.log(Level.FINE, "JCS - Returning object from cache: {0}", getCacheKey());
+                finishLoading(LoadResult.SUCCESS);
+                return;
+            }
+
             // try to load object from remote resource
             if (loadObject()) {
@@ -306,5 +308,5 @@
             }
 
-            HttpURLConnection urlConn = getURLConnection();
+            HttpURLConnection urlConn = getURLConnection(getUrl());
 
             if (isObjectLoadable()  &&
@@ -314,4 +316,13 @@
             if (isObjectLoadable() && attributes.getEtag() != null) {
                 urlConn.addRequestProperty("If-None-Match", attributes.getEtag());
+            }
+
+            // follow redirects
+            for (int i = 0; i < 5; i++) {
+                if (urlConn.getResponseCode() == 302) {
+                    urlConn = getURLConnection(new URL(urlConn.getHeaderField("Location")));
+                } else {
+                    break;
+                }
             }
             if (urlConn.getResponseCode() == 304) {
@@ -447,6 +458,6 @@
     }
 
-    private HttpURLConnection getURLConnection() throws IOException {
-        HttpURLConnection urlConn = (HttpURLConnection) getUrl().openConnection();
+    private HttpURLConnection getURLConnection(URL url) throws IOException {
+        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
         urlConn.setRequestProperty("Accept", "text/html, image/png, image/jpeg, image/gif, */*");
         urlConn.setReadTimeout(readTimeout); // 30 seconds read timeout
@@ -465,6 +476,13 @@
 
     private boolean isCacheValidUsingHead() throws IOException {
-        HttpURLConnection urlConn = getURLConnection();
+        HttpURLConnection urlConn = getURLConnection(getUrl());
         urlConn.setRequestMethod("HEAD");
+        for (int i = 0; i < 5; i++) {
+            if (urlConn.getResponseCode() == 302) {
+                urlConn = getURLConnection(new URL(urlConn.getHeaderField("Location")));
+            } else {
+                break;
+            }
+        }
         long lastModified = urlConn.getLastModified();
         return (attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getRequestProperty("ETag"))) ||
Index: trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java	(revision 8647)
+++ trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java	(revision 8649)
@@ -37,8 +37,10 @@
  */
 public class TemplatedWMSTileSource extends TMSTileSource implements TemplatedTileSource {
-    private Map<String, String> headers = new ConcurrentHashMap<>();
+    private final Map<String, String> headers = new ConcurrentHashMap<>();
     private final Set<String> serverProjections;
     private EastNorth topLeftCorner;
     private Bounds worldBounds;
+    private int[] tileXMax;
+    private int[] tileYMax;
 
     private static final Pattern PATTERN_HEADER  = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
@@ -89,4 +91,13 @@
         EastNorth max = proj.latlon2eastNorth(worldBounds.getMax());
         this.topLeftCorner = new EastNorth(min.east(), max.north());
+
+        LatLon bottomRight = new LatLon(worldBounds.getMinLat(), worldBounds.getMaxLon());
+        tileXMax = new int[getMaxZoom() + 1];
+        tileYMax = new int[getMaxZoom() + 1];
+        for(int zoom = getMinZoom(); zoom <= getMaxZoom(); zoom++) {
+            TileXY maxTileIndex = latLonToTileXY(bottomRight.toCoordinate(), zoom);
+            tileXMax[zoom] = maxTileIndex.getXIndex();
+            tileYMax[zoom] = maxTileIndex.getYIndex();
+        }
     }
 
@@ -229,6 +240,5 @@
     @Override
     public int getTileXMax(int zoom) {
-        LatLon bottomRight = new LatLon(worldBounds.getMinLat(), worldBounds.getMaxLon());
-        return latLonToTileXY(bottomRight.toCoordinate(), zoom).getXIndex();
+        return tileXMax[zoom];
     }
 
@@ -240,6 +250,5 @@
     @Override
     public int getTileYMax(int zoom) {
-        LatLon bottomRight = new LatLon(worldBounds.getMinLat(), worldBounds.getMaxLon());
-        return latLonToTileXY(bottomRight.toCoordinate(), zoom).getYIndex();
+        return tileYMax[zoom];
     }
 
