You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2013/04/15 23:12:01 UTC

[01/10] git commit: Pass a null CompressionAnalyzer as it is not used

Updated Branches:
  refs/heads/master 6fd011e7d -> 054770724


Pass a null CompressionAnalyzer as it is not used


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/e97c4021
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/e97c4021
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/e97c4021

Branch: refs/heads/master
Commit: e97c402168a84fa58fac5105d28038fcd9845957
Parents: 55003bd
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:34:20 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../integration/appfolder/AppFolderTests.groovy    |    2 +-
 .../ResponseCompressionAnalyzerTest.groovy         |    2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e97c4021/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/appfolder/AppFolderTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/appfolder/AppFolderTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/appfolder/AppFolderTests.groovy
index 57818a5..d183f03 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/appfolder/AppFolderTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/appfolder/AppFolderTests.groovy
@@ -58,7 +58,7 @@ class AppFolderTests extends GroovyTapestryCoreTestCase
         // Ony one image on page
         String assetURL = getAttribute("//img/@src")
 
-        assert assetURL.startsWith("/t5app/assets/")
+        assert assetURL.startsWith("/t5app/asset/")
 
         assertDownloadedAsset assetURL, "src/test/appfolder/images/t5-logo.png"
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e97c4021/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
index f702e2e..bdd56d5 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
@@ -17,7 +17,7 @@ class ResponseCompressionAnalyzerTest extends TestBase {
 
     replay()
 
-    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true, compressionAnalyzer)
+    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true, null)
 
     assert rca.isGZipSupported() == false
 


[09/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/be3e5817
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/be3e5817
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/be3e5817

Branch: refs/heads/master
Commit: be3e581771e1c932c621ce553f9bf2f5102cbb85
Parents: 3c0cb49
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 21:57:22 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../assets/ContextAssetRequestHandlerTest.java     |   29 ---------------
 1 files changed, 0 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/be3e5817/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
index 3836bff..767ed12 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
@@ -14,13 +14,7 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
-import org.apache.tapestry5.internal.services.ResourceStreamer;
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.test.TestBase;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
-import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -47,27 +41,4 @@ public class ContextAssetRequestHandlerTest extends TestBase
         assertFalse(handler.handleAssetRequest(null, null, "fake-checksum/" + path),
                 "Handler should return false for invalid path.");
     }
-
-    @Test
-    public void invalid_checksums_are_treated_as_if_missing() throws IOException
-    {
-        ResourceStreamer streamer = newMock(ResourceStreamer.class);
-        AssetChecksumGenerator gen = newMock(AssetChecksumGenerator.class);
-        Resource root = newMock(Resource.class);
-        Resource r = newMock(Resource.class);
-        Request req = newMock(Request.class);
-        Response res = newMock(Response.class);
-
-        expect(root.forFile("folder/icon.png")).andReturn(r);
-        expect(r.exists()).andReturn(true);
-        expect(gen.generateChecksum(r)).andReturn("def");
-
-        replay();
-
-        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, root);
-
-        assertFalse(h.handleAssetRequest(req, res, "abc/folder/icon.png"));
-
-        verify();
-    }
 }


[04/10] URLs for compressed assets are now distinct from uncompressed assets

Posted by hl...@apache.org.
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
index 4bf9597..95642ce 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
@@ -28,7 +28,8 @@ import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONLiteral;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.AssetSource;
-import org.apache.tapestry5.services.PathConstructor;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration;
 import org.apache.tapestry5.services.javascript.ModuleManager;
@@ -39,10 +40,13 @@ import java.util.Set;
 
 public class ModuleManagerImpl implements ModuleManager
 {
-    private final String requireConfig;
+
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
 
     private final Asset requireJS;
 
+    private final Map<String, JavaScriptModuleConfiguration> configuration;
+
     private final Messages globalMessages;
 
     private final boolean compactJSON;
@@ -56,7 +60,11 @@ public class ModuleManagerImpl implements ModuleManager
     // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null.
     private final Map<String, Resource> cache = CollectionFactory.newConcurrentMap();
 
-    public ModuleManagerImpl(PathConstructor constructor,
+    private final boolean devMode;
+
+    private final AssetPathConstructor assetPathConstructor;
+
+    public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer,
                              AssetSource assetSource,
                              @Path("${" + SymbolConstants.REQUIRE_JS + "}")
                              Asset requireJS,
@@ -66,23 +74,28 @@ public class ModuleManagerImpl implements ModuleManager
                              @Symbol(SymbolConstants.COMPACT_JSON)
                              boolean compactJSON,
                              @Symbol(SymbolConstants.PRODUCTION_MODE)
-                             boolean productionMode)
+                             boolean productionMode,
+                             AssetPathConstructor assetPathConstructor)
     {
+        this.compressionAnalyzer = compressionAnalyzer;
         this.requireJS = requireJS;
+        this.configuration = configuration;
         this.globalMessages = globalMessages;
         this.compactJSON = compactJSON;
+        this.assetPathConstructor = assetPathConstructor;
 
-        this.requireConfig = buildRequireJSConfig(constructor.constructClientPath("modules", ""), configuration, !productionMode);
+        this.devMode = !productionMode;
 
         classpathRoot = assetSource.resourceForPath("");
-
         extensions = CollectionFactory.newSet("js");
 
         extensions.addAll(streamableResourceSource.fileExtensionsForContentType("text/javascript"));
     }
 
-    private String buildRequireJSConfig(String baseURL, Map<String, JavaScriptModuleConfiguration> configuration, boolean devMode)
+    private String buildRequireJSConfig()
     {
+        String baseURL = getBaseURL();
+
         JSONObject config = new JSONObject("baseUrl", baseURL);
 
         // In DevMode, wait up to five minutes for a script, as the developer may be using the debugger.
@@ -110,6 +123,11 @@ public class ModuleManagerImpl implements ModuleManager
         return String.format("requirejs.config(%s);\n", config.toString(compactJSON));
     }
 
+    private String getBaseURL()
+    {
+        return assetPathConstructor.constructAssetPath("module", compressionAnalyzer.isGZipSupported());
+    }
+
     private void addModuleToConfig(JSONObject config, String name, JavaScriptModuleConfiguration module)
     {
         JSONObject shimConfig = config.in("shim");
@@ -159,7 +177,10 @@ public class ModuleManagerImpl implements ModuleManager
 
         Element element = body.element("script", "type", "text/javascript");
 
-        element.raw(requireConfig);
+        // Build it each time because we don't know if the client supports GZip or not, and
+        // (in development mode) URLs for some referenced assets could change (due to URLs
+        // containing a checksum on the resource content).
+        element.raw(buildRequireJSConfig());
 
         StringBuilder content = new StringBuilder(1000);
 
@@ -199,7 +220,6 @@ public class ModuleManagerImpl implements ModuleManager
             return resource;
         }
 
-
         // Tack on a fake extension; otherwise modules whose name includes a '.' get mangled
         // by Resource.withExtension().
         String baseName = String.format("/META-INF/modules/%s.EXT", moduleName);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
index 0fc0738..bb1061f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
@@ -30,7 +30,7 @@ import java.util.Locale;
  * the contents of the virtual resource.
  *
  * @see org.apache.tapestry5.services.javascript.ModuleManager
- * @see org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+ * @see org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
  * @since 5.4
  */
 public abstract class VirtualResource implements Resource

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
new file mode 100644
index 0000000..15ee1b7
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
@@ -0,0 +1,32 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http:#www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.services;
+
+/**
+ * Identifies a virtual foldre and a path within that folder.
+ *
+ * @since 5.4
+ * @deprecated Deprecated in 5.4 (see notes in {@link ClasspathAssetAliasManager}).
+ */
+public class AssetAlias
+{
+    public final String virtualFolder, path;
+
+    public AssetAlias(String virtualFolder, String path)
+    {
+        this.virtualFolder = virtualFolder;
+        this.path = path;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
index e85234e..b540780 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
@@ -41,22 +41,22 @@ import java.util.Map;
  * <p/>
  * Tapestry automatically contributes a number of mappings: for the application root package itself (as alias "app") and
  * for each library (via {@link ComponentClassResolver#getFolderToPackageMapping()});
+ *
+ * @deprecated Deprecated in 5.4, with no replacement. This will no longer be used in Tapestry 5.5, as all classpath assets
+ *             will need to be under the {@code META-INF/assets} folder (but may be maintained for compatibility reasons until 5.6).
  */
 @UsesMappedConfiguration(String.class)
 public interface ClasspathAssetAliasManager
 {
     /**
-     * Takes a resource path to a classpath resource and adds the asset path prefix to the path. May also convert part
-     * of the path to an alias (based on the manager's configuration).
-     * <p/>
-     * Note: this method's signature changed incompatibly in Tapestry 5.4.
+     * Takes a classpath resource and determines the proper alias for it based on the mappings contributed to the service.
      *
      * @param resource
      *         classpath resource
      * @return URL ready to send to the client
      */
-    @IncompatibleChange(release = "5.4", details = "parameter changed from String to Resource")
-    String toClientURL(Resource resource);
+    @IncompatibleChange(release = "5.4", details = "parameter changed from String to Resource, renamed from toClientURL() to better identify purpose")
+    AssetAlias extractAssetAlias(Resource resource);
 
     /**
      * Returns the mappings used by the service: the keys are the folder aliases (i.e, "corelib")

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
index ffe0239..d9a61b2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
@@ -1,4 +1,4 @@
-// Copyright 2009, 2011, 2012 The Apache Software Foundation
+// Copyright 2009-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@ package org.apache.tapestry5.services;
 import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 /**
- * Used to determine if the client supports GZIP compression of the response.
+ * Used to determine if the client supports GZip compression of the response.
  *
  * @see CompressionAnalyzer
+ * @see org.apache.tapestry5.SymbolConstants#GZIP_COMPRESSION_ENABLED
  * @since 5.1.0.0
  */
 public interface ResponseCompressionAnalyzer
@@ -31,4 +32,14 @@ public interface ResponseCompressionAnalyzer
      * @return true if gzip is supported by client
      */
     boolean isGZipSupported();
+
+    /**
+     * Uses {@link CompressionAnalyzer} to determine if the content is compressable, but only if the request
+     * indicates the client supports compression.
+     *
+     * @param contentType
+     * @return true if the content can be compressed for the current request
+     * @since 5.4
+     */
+    boolean isGZipEnabled(String contentType);
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
index 17fb2b7..ffb4d2c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
@@ -14,14 +14,13 @@
 
 package org.apache.tapestry5.services.assets;
 
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
 
 import java.io.IOException;
 
 /**
- * Encapsulates the logic or creating the path portion of an asset URL, including
- * the application version.
+ * Encapsulates the logic or creating the path portion of an asset URL, including hooking the {@link org.apache.tapestry5.services.AssetPathConverter}
+ * into the generation.
  *
  * @see org.apache.tapestry5.services.PathConstructor
  * @since 5.2.0
@@ -30,31 +29,38 @@ public interface AssetPathConstructor
 {
     /**
      * Constructs an asset URL path from the virtual folder and path (within the virtual folder).
+     * After constructing the string (and honoring the {@link org.apache.tapestry5.SymbolConstants#ASSET_URL_FULL_QUALIFIED}
+     * symbol), the result is passed through the {@link org.apache.tapestry5.services.AssetPathConverter}.
      *
      * @param virtualFolder
      *         corresponds to a {@link AssetRequestHandler} contributed to the AssetDispatcher service
      * @param path
-     *         within the virtual folder (should <em>not</em> start with a slash). May be the empty string.
-     *         When non-blank, separated from the rest of the path with a slash.
+     *         a path that can be used to identify the underlying {@link org.apache.tapestry5.ioc.Resource} or
+     *         or re-acquire the {@link StreamableResource}; this will be the final portion of the URL, after
+     *         the appropriate prefix (based on whether the resource is compressed or not) and the checksum for the
+     *         resource
      * @param resource
-     *         underlying resource for the asset path, used to compute checksums (since 5.4)
+     *         underlying resource for the asset path; the checksum portion of the URL is obtained from the resource
      * @return path portion of asset URL, which is everything needed by the {@link org.apache.tapestry5.internal.services.AssetDispatcher}
      *         to find and stream the resource
+     * @see StreamableResourceSource
      */
-    @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may not be thrown")
-    String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException;
+    @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may now be thrown")
+    String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException;
 
     /**
-     * Constructs an asset path for a aggregated {@linkplain org.apache.tapestry5.services.javascript.JavaScriptStack stack}.
+     * Generates a base URL for a virtual folder (this exists mostly for {@link org.apache.tapestry5.services.javascript.ModuleManager}
+     * and {@link org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler}). Uses much of the same logic
+     * as {@link #constructAssetPath(String, String, StreamableResource)}, including
+     * {@link org.apache.tapestry5.SymbolConstants#ASSET_URL_FULL_QUALIFIED} and the {@link org.apache.tapestry5.services.AssetPathConverter}.
      *
-     * @param localeName
-     *         name of the locale
-     * @param path
-     *         based on the name of the core stack
-     * @param resource
-     *         the aggregated stack (used when generating the checksum)
-     * @return path that identifies the checksum, locale, and path
+     * @param virtualFolder
+     *         folder that will be used to select a {@link }
+     * @param compressed
+     *         build a path that indicates GZip compression
+     * @return complete path
      * @since 5.4
      */
-    String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException;
+    String constructAssetPath(String virtualFolder, boolean compressed);
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
index c0acce3..e1eb7ed 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
@@ -49,6 +49,9 @@ public interface AssetRequestHandler
      * The handler should return true if it provided a response. If the handler returns false, this indicates that the
      * extra path did not identify a known asset (virtual or otherwise) and the AssetDispatcher service should send a
      * {@link HttpServletResponse#SC_NOT_FOUND} response.
+     * <p/>
+     * Starting in Tapestry 5.4, the handler is informed by the {@link org.apache.tapestry5.services.AssetRequestDispatcher}
+     * whether or not the content should be compressed (this is determined based on information in the URL).
      *
      * @param request
      *         incoming asset request
@@ -56,7 +59,8 @@ public interface AssetRequestHandler
      *         used to send a response to client
      * @param extraPath
      *         additional path to identify the specific asset
-     * @return true if request handler, false if asset not found
+     * @return true if request was handled (and response sent), false if asset not found
+     * @see org.apache.tapestry5.TapestryConstants#COMPRESS_CONTENT
      */
     boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index 797e464..d727792 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -75,7 +75,7 @@ public class AssetsModule
         configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
         configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
 
-        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
+        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "asset");
         configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
     }
 
@@ -86,10 +86,11 @@ public class AssetsModule
     public StreamableResourceSource enableCompression(StreamableResourceSource delegate,
                                                       @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
                                                       boolean gzipEnabled, @Symbol(SymbolConstants.MIN_GZIP_SIZE)
-                                                      int compressionCutoff)
+                                                      int compressionCutoff,
+                                                      AssetChecksumGenerator checksumGenerator)
     {
         return gzipEnabled
-                ? new SRSCompressingInterceptor(delegate, compressionCutoff)
+                ? new SRSCompressingInterceptor(delegate, compressionCutoff, checksumGenerator)
                 : null;
     }
 
@@ -189,12 +190,12 @@ public class AssetsModule
 
     @Marker(ContextProvider.class)
     public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
-
                                                         AssetPathConstructor assetPathConstructor,
-
-                                                        AssetPathConverter converter)
+                                                        ResponseCompressionAnalyzer compressionAnalyzer,
+                                                        ResourceChangeTracker resourceChangeTracker,
+                                                        StreamableResourceSource streamableResourceSource)
     {
-        return new ContextAssetFactory(assetPathConstructor, globals.getContext(), converter);
+        return new ContextAssetFactory(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor, globals.getContext());
     }
 
     @Contribute(ClasspathAssetAliasManager.class)
@@ -222,8 +223,6 @@ public class AssetsModule
                                                       @Autobuild
                                                       StackAssetRequestHandler stackAssetRequestHandler,
 
-                                                      AssetChecksumGenerator assetChecksumGenerator,
-
                                                       ClasspathAssetAliasManager classpathAssetAliasManager,
                                                       ResourceStreamer streamer,
                                                       AssetSource assetSource)
@@ -234,11 +233,11 @@ public class AssetsModule
         {
             String path = mappings.get(folder);
 
-            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetChecksumGenerator, assetSource, path));
+            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetSource, path));
         }
 
         configuration.add(RequestConstants.CONTEXT_FOLDER,
-                new ContextAssetRequestHandler(streamer, assetChecksumGenerator, contextAssetFactory.getRootResource()));
+                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
 
         configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
index 3edeff5..ab6cd16 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
@@ -31,7 +31,7 @@ import java.io.OutputStream;
 public interface StreamableResource
 {
     /**
-     * Describes the underlying {@link Resource} (or resources} for this streamble resource; expressly used
+     * Describes the underlying {@link Resource} (or resources} for this streamable resource; expressly used
      * as part of the object's {@code toString()}.
      */
     String getDescription();

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
index cd19a5e..39417a3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
@@ -28,13 +28,13 @@ import org.apache.tapestry5.ioc.OrderedConfiguration;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
-import org.apache.tapestry5.ioc.annotations.Primary;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.FactoryDefaults;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.compatibility.Compatibility;
 import org.apache.tapestry5.services.compatibility.Trait;
 import org.apache.tapestry5.services.messages.ComponentMessagesSource;
@@ -130,6 +130,13 @@ public class JavaScriptModule
         return environmentalBuilder.build(JavaScriptSupport.class);
     }
 
+    @Contribute(Dispatcher.class)
+    @AssetRequestDispatcher
+    public static void provideModuleHandler(MappedConfiguration<String, AssetRequestHandler> configuration)
+    {
+        configuration.addInstance("module", ModuleAssetRequestHandler.class);
+    }
+
     /**
      * Adds page render filters, each of which provides an {@link org.apache.tapestry5.annotations.Environmental}
      * service. Filters
@@ -210,12 +217,6 @@ public class JavaScriptModule
         configuration.add("JavaScriptSupport", javascriptSupport, "after:DocumentLinker");
     }
 
-    @Contribute(Dispatcher.class)
-    @Primary
-    public static void handleModuleRequests(OrderedConfiguration<Dispatcher> configuration)
-    {
-        configuration.addInstance("Module", ModuleDispatcher.class, "before:PageRender");
-    }
 
     @Contribute(ModuleManager.class)
     public static void setupBaseModules(MappedConfiguration<String, Object> configuration,

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
index 3b2f4c1..f702e2e 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
@@ -17,7 +17,7 @@ class ResponseCompressionAnalyzerTest extends TestBase {
 
     replay()
 
-    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true)
+    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true, compressionAnalyzer)
 
     assert rca.isGZipSupported() == false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
index 7e41fbf..9065587 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
@@ -31,7 +31,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
     replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", pc, gen)
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", null, pc, pathConverter)
 
     assert apc.constructAssetPath("virt", "extra.png", virtExtra) == "/assets/virt/abc/extra.png"
 
@@ -58,7 +58,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
     replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", pc, gen)
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", null, pc, pathConverter)
 
     assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/911/icon.gif"
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
index 3c974b7..56635c3 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
@@ -1,6 +1,6 @@
 package org.apache.tapestry5.services.javascript
 
-import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.PathConstructor
@@ -25,7 +25,7 @@ class ModuleAssetRequestHandlerTest extends TestBase {
 
         replay()
         
-        def handler = new ModuleDispatcher(null, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
         
         assertEquals handler.dispatch(request, response), false
 
@@ -61,7 +61,7 @@ class ModuleAssetRequestHandlerTest extends TestBase {
         
         replay()
 
-        def handler = new ModuleDispatcher(manager, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
         
         assertEquals handler.dispatch(request, response), false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
index 35b407a..ca72a21 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
@@ -1,6 +1,6 @@
 package org.apache.tapestry5.services.javascript
 
-import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.PathConstructor
@@ -22,7 +22,7 @@ class ModuleDispatcherTests extends TestBase {
 
         replay()
 
-        def handler = new ModuleDispatcher(null, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
 
         assertEquals handler.dispatch(request, null), false
 
@@ -56,7 +56,7 @@ class ModuleDispatcherTests extends TestBase {
 
         replay()
 
-        def handler = new ModuleDispatcher(manager, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
 
         assertEquals handler.dispatch(request, null), false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
index 776ad50..fcff983 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
@@ -14,17 +14,12 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
-import org.apache.tapestry5.services.BaseURLSource;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.apache.tapestry5.services.PathConstructor;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -33,8 +28,6 @@ import java.util.Map;
 
 public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 {
-    private static final String APP_VERSION = "1.2.3";
-
     public Map<String, String> configuration()
     {
         Map<String, String> configuration = CollectionFactory.newMap();
@@ -55,7 +48,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 
         try
         {
-            new ClasspathAssetAliasManagerImpl(null, configuration);
+            new ClasspathAssetAliasManagerImpl(configuration);
             unreachable();
         } catch (RuntimeException ex)
         {
@@ -75,34 +68,26 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
         expected.put("tapestry-internal", "org/apache/tapestry5/internal");
         expected.put("mylib", "com/example/mylib");
 
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(null, configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
 
         assertEquals(manager.getMappings(), expected);
     }
 
     @Test(dataProvider = "to_client_url_data")
-    public void to_client_url(String resourcePath, String expectedClientURL) throws IOException
+    public void to_client_url(String resourcePath, String expectedFolder, String expectedPath) throws IOException
     {
-        Request request = mockRequest();
         Resource r = mockResource();
 
         expect(r.getPath()).andReturn(resourcePath);
 
-        BaseURLSource baseURLSource = newMock(BaseURLSource.class);
-        PathConstructor pc = newMock(PathConstructor.class);
-        AssetChecksumGenerator acg = newMock(AssetChecksumGenerator.class);
-
-        expect(pc.constructClientPath("assets", "")).andReturn("/ctx/assets/");
-        expect(acg.generateChecksum(r)).andReturn("abcde");
-
         replay();
 
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(
-                new AssetPathConstructorImpl(request,
-                        baseURLSource, false, "assets", pc, acg), configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
+
+        AssetAlias alias = manager.extractAssetAlias(r);
 
-        String expectedPath = "/ctx/assets/" + expectedClientURL.replace("#", "abcde");
-        assertEquals(manager.toClientURL(r), expectedPath);
+        assertEquals(alias.virtualFolder, expectedFolder);
+        assertEquals(alias.path, expectedPath);
 
         verify();
     }
@@ -110,8 +95,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     @Test
     public void failure_if_path_not_in_mapped_alias_folder()
     {
-        AssetPathConstructor pc = newMock(AssetPathConstructor.class);
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(pc, configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
         Resource resource = mockResource();
 
         expect(resource.getPath()).andReturn("org/example/icons/flag.gif").atLeastOnce();
@@ -120,7 +104,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 
         try
         {
-            manager.toClientURL(resource);
+            manager.extractAssetAlias(resource);
             unreachable();
         } catch (UnknownValueException ex)
         {
@@ -138,10 +122,11 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     {
         return new Object[][]
                 {
-                        {"com/example/mylib/Foo.bar", "mylib/#/Foo.bar"},
-                        {"com/example/mylib/nested/Foo.bar", "mylib/#/nested/Foo.bar"},
-                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal/#/Foo.bar"},
-                        {"org/apache/tapestry5/Foo.bar", "tapestry/#/Foo.bar"},};
+                        {"com/example/mylib/Foo.bar", "mylib", "Foo.bar"},
+                        {"com/example/mylib/nested/Foo.bar", "mylib", "nested/Foo.bar"},
+                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal", "Foo.bar"},
+                        {"org/apache/tapestry5/Foo.bar", "tapestry", "Foo.bar"}
+                };
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
deleted file mode 100644
index bdddceb..0000000
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2006, 2007, 2008, 2009, 2011, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services;
-
-import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.internal.test.InternalBaseTestCase;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
-import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.testng.annotations.Test;
-
-public class ClasspathAssetFactoryTest extends InternalBaseTestCase
-{
-    private final IdentityAssetPathConverter converter = new IdentityAssetPathConverter();
-
-    @Test
-    public void asset_client_URL_is_cached()
-    {
-        Resource r = new ClasspathResource("foo/Bar.txt");
-
-        ClasspathAssetAliasManager aliasManager = mockClasspathAssetAliasManager();
-
-        String expectedClientURL = "/context/asset/foo/Bar.txt";
-
-        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
-
-        replay();
-
-        ClasspathAssetFactory factory = new ClasspathAssetFactory(aliasManager, converter);
-
-        Asset asset = factory.createAsset(r);
-
-        assertEquals(asset.toClientURL(), expectedClientURL);
-
-        // Now, to check the cache:
-
-        assertEquals(asset.toClientURL(), expectedClientURL);
-
-        verify();
-    }
-
-    @Test
-    public void simple_asset_client_URL()
-    {
-        ClasspathAssetAliasManager aliasManager = mockClasspathAssetAliasManager();
-
-        Resource r = new ClasspathResource("foo/Bar.txt");
-
-        String expectedClientURL = "/context/asset/foo/Bar.txt";
-
-        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
-
-        replay();
-
-        AssetFactory factory = new ClasspathAssetFactory(aliasManager, new IdentityAssetPathConverter());
-
-        Asset asset = factory.createAsset(r);
-
-        assertSame(asset.getResource(), r);
-        assertEquals(asset.toClientURL(), expectedClientURL);
-        assertEquals(asset.toString(), expectedClientURL);
-
-        verify();
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
deleted file mode 100644
index cb0fa33..0000000
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2006, 2007, 2009, 2012, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services;
-
-import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.internal.test.InternalBaseTestCase;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.Context;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-
-public class ContextAssetFactoryTest extends InternalBaseTestCase
-{
-    private final IdentityAssetPathConverter converter = new IdentityAssetPathConverter();
-
-    @Test
-    public void root_resource()
-    {
-        Context context = mockContext();
-        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
-
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(apc, context, converter);
-
-        assertEquals(factory.getRootResource().toString(), "context:/");
-
-        verify();
-    }
-
-    @Test
-    public void asset_client_URL() throws IOException
-    {
-        Context context = mockContext();
-        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
-
-        Resource r = new ContextResource(context, "foo/Bar.txt");
-
-        String expectedURL = "/expected-url";
-
-        expect(apc.constructAssetPath("ctx", "foo/Bar.txt", r)).andReturn(expectedURL).atLeastOnce();
-
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(apc, context, new IdentityAssetPathConverter());
-
-        Asset asset = factory.createAsset(r);
-
-        assertSame(asset.getResource(), r);
-
-        assertSame(asset.toClientURL(), expectedURL);
-
-        // In real life, toString() is the same as toClientURL(), but we're testing
-        // that the optimize method is getting called, basically.
-        assertSame(asset.toString(), expectedURL);
-
-        verify();
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
index bdb745c..3836bff 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
@@ -42,7 +42,7 @@ public class ContextAssetRequestHandlerTest extends TestBase
     @Test(dataProvider = "invalid_paths")
     public void ensure_assets_are_rejected(String path) throws IOException
     {
-        ContextAssetRequestHandler handler = new ContextAssetRequestHandler(null, null, null);
+        ContextAssetRequestHandler handler = new ContextAssetRequestHandler(null, null);
 
         assertFalse(handler.handleAssetRequest(null, null, "fake-checksum/" + path),
                 "Handler should return false for invalid path.");
@@ -64,7 +64,7 @@ public class ContextAssetRequestHandlerTest extends TestBase
 
         replay();
 
-        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, gen, root);
+        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, root);
 
         assertFalse(h.handleAssetRequest(req, res, "abc/folder/icon.png"));
 


[02/10] git commit: Adjust for change to default asset path prefix

Posted by hl...@apache.org.
Adjust for change to default asset path prefix


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/55003bd3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/55003bd3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/55003bd3

Branch: refs/heads/master
Commit: 55003bd3e08f3d60681831235095d4c5c6820876
Parents: 5a4c003
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:33:50 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../tapestry5/integration/app1/LibraryTests.groovy |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/55003bd3/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
index 67a72d8..07263c4 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
@@ -19,7 +19,7 @@ class LibraryTests extends GroovyTapestryCoreTestCase
 
         String assetURL = getAttribute("//img[@id='t5logo']/@src")
 
-        def pattern = ~"/assets/lib/alpha/\\w+/pages/tapestry\\.png"
+        def pattern = ~"/asset/lib/alpha/\\w+/pages/tapestry\\.png"
 
         assert pattern.matcher(assetURL).matches()
 


[07/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/05477072
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/05477072
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/05477072

Branch: refs/heads/master
Commit: 05477072442e72550e2acb59c93f420140103a7f
Parents: be3e581
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 22:01:02 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../ModuleAssetRequestHandlerTests.groovy          |   43 ++++++++++
 .../javascript/ModuleDispatcherTests.groovy        |   65 ---------------
 2 files changed, 43 insertions(+), 65 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/05477072/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
new file mode 100644
index 0000000..5b24b1c
--- /dev/null
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
@@ -0,0 +1,43 @@
+package org.apache.tapestry5.services.javascript
+
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
+import org.apache.tapestry5.ioc.internal.QuietOperationTracker
+import org.apache.tapestry5.ioc.test.TestBase
+import org.testng.annotations.DataProvider
+import org.testng.annotations.Test
+
+class ModuleAssetRequestHandlerTests extends TestBase {
+
+    @Test(dataProvider = "unknownPaths")
+    void "invalid extension is ignored"(extraPath) {
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
+
+        assert handler.handleAssetRequest(null, null, extraPath) == false
+    }
+
+    @DataProvider
+    Object[][] unknownPaths() {
+        [
+            "foo/bar.xyz",
+            "foo",
+            "foo/bar",
+            ""
+        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
+    }
+
+    @Test
+    void "returns false if no module is found"() {
+
+        def manager = newMock ModuleManager
+
+        expect(manager.findResourceForModule("foo/bar")).andReturn null
+
+        replay()
+
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
+
+        assert handler.handleAssetRequest(null, null, "foo/bar.js") == false
+
+        verify()
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/05477072/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
deleted file mode 100644
index ca72a21..0000000
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.apache.tapestry5.services.javascript
-
-import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
-import org.apache.tapestry5.ioc.internal.QuietOperationTracker
-import org.apache.tapestry5.ioc.test.TestBase
-import org.apache.tapestry5.services.PathConstructor
-import org.apache.tapestry5.services.Request
-import org.testng.annotations.DataProvider
-import org.testng.annotations.Test
-
-class ModuleDispatcherTests extends TestBase {
-
-    @Test(dataProvider = "unknownPaths")
-    void "invalid extension is ignored"(path) {
-        def pc = newMock PathConstructor
-
-        def request = newMock Request
-
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        expect(request.path).andReturn path
-
-        replay()
-
-        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
-
-        assertEquals handler.dispatch(request, null), false
-
-        verify()
-    }
-
-    @DataProvider
-    Object[][] unknownPaths() {
-        [
-            "foo/bar.xyz",
-            "foo",
-            "foo/bar",
-            ""
-        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
-    }
-
-    @Test
-    void "returns false if no module is found"() {
-
-        def pc = newMock PathConstructor
-
-        def manager = newMock ModuleManager
-
-        def request = newMock Request
-
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        expect(request.path).andReturn("/modules/foo/bar.js")
-
-        expect(manager.findResourceForModule("foo/bar")).andReturn null
-
-        replay()
-
-        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
-
-        assertEquals handler.dispatch(request, null), false
-
-        verify()
-    }
-}


[03/10] git commit: Fix broken tests

Posted by hl...@apache.org.
Fix broken tests

Remove unwanted access to HttpServletRequest when using the PageTester


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/5a4c0039
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/5a4c0039
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/5a4c0039

Branch: refs/heads/master
Commit: 5a4c0039b4c03920ab0dfda7e3e6339beb591b40
Parents: 154dac9
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:24:22 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../tapestry5/internal/test/PageTesterModule.java  |   31 ++++++++++++---
 1 files changed, 25 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5a4c0039/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
index 12455b4..656187e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2007-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,15 +17,14 @@ package org.apache.tapestry5.internal.test;
 import org.apache.tapestry5.internal.services.CookieSink;
 import org.apache.tapestry5.internal.services.CookieSource;
 import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.ObjectLocator;
 import org.apache.tapestry5.ioc.OrderedConfiguration;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
 import org.apache.tapestry5.ioc.annotations.Local;
 import org.apache.tapestry5.ioc.services.ServiceOverride;
-import org.apache.tapestry5.services.MarkupRendererFilter;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.RequestFilter;
-import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 import org.apache.tapestry5.test.PageTester;
 
 /**
@@ -46,7 +45,7 @@ public class PageTesterModule
     @Contribute(ServiceOverride.class)
     public static void setupTestableOverrides(MappedConfiguration<Class, Object> configuration, @Local
     TestableRequest request, @Local
-    TestableResponse response)
+                                              TestableResponse response, final ObjectLocator locator)
     {
         configuration.add(Request.class, request);
         configuration.add(Response.class, response);
@@ -55,6 +54,26 @@ public class PageTesterModule
 
         configuration.add(CookieSink.class, cookies);
         configuration.add(CookieSource.class, cookies);
+
+        // With the significant changes to the handling of assets in 5.4, we introduced a problem:
+        // We were checking at page render time whether to generate URLs for normal or compressed
+        // assets and that peeked at the HttpServletRequest global, which isn't set up by PageTester.
+        // What we're doing here is using a hacked version of that code to force GZip support
+        // on.
+        configuration.add(ResponseCompressionAnalyzer.class, new ResponseCompressionAnalyzer()
+        {
+            @Override
+            public boolean isGZipEnabled(String contentType)
+            {
+                return locator.getObject(CompressionAnalyzer.class, null).isCompressable(contentType);
+            }
+
+            @Override
+            public boolean isGZipSupported()
+            {
+                return true;
+            }
+        });
     }
 
     public static void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration)


[02/10] git commit: Adjust for change to default asset path prefix

Posted by hl...@apache.org.
Adjust for change to default asset path prefix


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/55003bd3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/55003bd3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/55003bd3

Branch: refs/heads/master
Commit: 55003bd3e08f3d60681831235095d4c5c6820876
Parents: 5a4c003
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:33:50 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../tapestry5/integration/app1/LibraryTests.groovy |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/55003bd3/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
index 67a72d8..07263c4 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
@@ -19,7 +19,7 @@ class LibraryTests extends GroovyTapestryCoreTestCase
 
         String assetURL = getAttribute("//img[@id='t5logo']/@src")
 
-        def pattern = ~"/assets/lib/alpha/\\w+/pages/tapestry\\.png"
+        def pattern = ~"/asset/lib/alpha/\\w+/pages/tapestry\\.png"
 
         assert pattern.matcher(assetURL).matches()
 


[10/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/1a97747f
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/1a97747f
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/1a97747f

Branch: refs/heads/master
Commit: 1a97747f80a41bcac4e60afc1a61ac1ba3a9fba9
Parents: e97c402
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:41:02 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../ModuleAssetRequestHandlerTest.groovy           |   46 +++-----------
 1 files changed, 10 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1a97747f/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
index 56635c3..eed6325 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
@@ -3,9 +3,6 @@ package org.apache.tapestry5.services.javascript
 import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
-import org.apache.tapestry5.services.PathConstructor
-import org.apache.tapestry5.services.Request
-import org.apache.tapestry5.services.Response
 import org.testng.annotations.DataProvider
 import org.testng.annotations.Test
 
@@ -13,57 +10,34 @@ class ModuleAssetRequestHandlerTest extends TestBase {
 
     @Test(dataProvider = "unknownPaths")
     void "invalid extension is ignored"(path) {
-        def pc = newMock PathConstructor
-        
-        def request = newMock Request
-        
-        def response = newMock Response
-        
-        expect(request.getPath()).andReturn path
 
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        replay()
-        
         def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
-        
-        assertEquals handler.dispatch(request, response), false
 
-        verify()
+        assertEquals handler.handleAssetRequest(null, null, path), false
     }
 
     @DataProvider
     Object[][] unknownPaths() {
         [
-            "foo/bar.xyz",
-            "foo",
-            "foo/bar",
-            ""
-        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
+            ["foo/bar.xyz"],
+            ["foo"],
+            ["foo/bar"],
+            [""]
+        ] as Object[][]
     }
 
     @Test
     void "returns false if no module is found"() {
 
-        def pc = newMock PathConstructor
-
         def manager = newMock ModuleManager
-        
-        def request = newMock Request
-        
-        def response = newMock Response
-        
-        expect(request.getPath()).andReturn "/modules/foo/bar.js"
-        
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-        
+
         expect(manager.findResourceForModule("foo/bar")).andReturn null
-        
+
         replay()
 
         def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
-        
-        assertEquals handler.dispatch(request, response), false
+
+        assertEquals handler.handleAssetRequest(null, null, "foo/bar.js"), false
 
         verify()
     }


[08/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/3c0cb494
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/3c0cb494
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/3c0cb494

Branch: refs/heads/master
Commit: 3c0cb4942e78717b0c615564bf08633e55eb23e6
Parents: 1a97747
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 21:56:52 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../assets/AssetPathConstructorImplTest.groovy     |   67 ++++++++-------
 1 files changed, 35 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3c0cb494/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
index 9065587..02ed33e 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
@@ -1,68 +1,71 @@
 package org.apache.tapestry5.internal.services.assets
 
-import org.apache.tapestry5.ioc.Resource
+import org.apache.tapestry5.internal.services.IdentityAssetPathConverter
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.BaseURLSource
 import org.apache.tapestry5.services.PathConstructor
 import org.apache.tapestry5.services.Request
 import org.apache.tapestry5.services.assets.AssetChecksumGenerator
+import org.apache.tapestry5.services.assets.CompressionStatus
+import org.apache.tapestry5.services.assets.StreamableResource
 import org.testng.annotations.Test
 
 class AssetPathConstructorImplTest extends TestBase {
 
-  @Test
-  void "construct an asset path with no application folder"() {
+    def pathConverter = new IdentityAssetPathConverter()
 
-    def request = newMock(Request)
+    @Test
+    void "construct an asset path with no application folder"() {
 
-    def baseURLSource = newMock(BaseURLSource)
+        def pc = newMock(PathConstructor)
 
-    def pc = newMock(PathConstructor)
+        def gen = newMock(AssetChecksumGenerator)
 
-    def gen = newMock(AssetChecksumGenerator)
+        def r = newMock(StreamableResource)
 
-    def virtExtra = newMock(Resource)
+        expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
-    def virtb = newMock(Resource)
+        expect(r.compression).andReturn(CompressionStatus.COMPRESSED)
 
-    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(r.checksum).andReturn("abc")
 
-    expect(gen.generateChecksum(virtExtra)).andReturn("abc")
+        replay()
 
-    replay()
+        def apc = new AssetPathConstructorImpl(null, null, false, "assets", "assets.gz", pc, pathConverter)
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", null, pc, pathConverter)
+        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets.gz/virt/abc/extra.png"
 
-    assert apc.constructAssetPath("virt", "extra.png", virtExtra) == "/assets/virt/abc/extra.png"
+        verify()
+    }
 
-    verify()
-  }
+    @Test
+    void "fully qualified path has base URL prepended"() {
+        def request = newMock(Request)
 
-  @Test
-  void "fully qualified path has base URL prepended"() {
-    def request = newMock(Request)
-    def baseURLSource = newMock(BaseURLSource)
+        def baseURLSource = newMock(BaseURLSource)
 
-    def pc = newMock(PathConstructor)
+        def pc = newMock(PathConstructor)
 
-    def gen = newMock(AssetChecksumGenerator)
+        def r = newMock(StreamableResource)
 
-    def r = newMock(Resource)
+        expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
-    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(request.secure).andReturn(false)
+        expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
 
-    expect(request.secure).andReturn(false)
-    expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
+        expect(r.compression).andReturn(CompressionStatus.NOT_COMPRESSABLE)
 
-    expect(gen.generateChecksum(r)).andReturn("911")
+        expect(r.checksum).andReturn("abc")
 
-    replay()
+        replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", null, pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", "assets.gz", pc, pathConverter)
 
-    assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/911/icon.gif"
+        assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/abc/icon.gif"
 
-    verify()
+        verify()
 
-  }
+    }
 }


[05/10] git commit: URLs for compressed assets are now distinct from uncompressed assets

Posted by hl...@apache.org.
URLs for compressed assets are now distinct from uncompressed assets

"/asset/" for normal assets, or "/asset.gz/" for compressed assets
Assets now know about the StreamableResource (necessary to get content type and content checksum)
Content checksum for compressed resources is now based on the compressed bytestream, not the uncompressed resource
Modules are now a special kind of asset
Did a better job of centralizing the logic for creating asset URLs

Tests are currently broken
Peformance may have been affected


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/154dac95
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/154dac95
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/154dac95

Branch: refs/heads/master
Commit: 154dac959c7c6bf264005ad32ba393c459b719dd
Parents: aa465fc
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 12:12:57 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../java/org/apache/tapestry5/SymbolConstants.java |    8 +-
 .../org/apache/tapestry5/TapestryConstants.java    |   19 ++-
 .../internal/services/AbstractAssetFactory.java    |   90 +++++++++++-
 .../internal/services/AssetDispatcher.java         |   20 +++-
 .../services/ClasspathAssetAliasManagerImpl.java   |   42 ++----
 .../internal/services/ClasspathAssetFactory.java   |   95 ++-----------
 .../internal/services/ContextAssetFactory.java     |  109 ++-------------
 .../internal/services/ResourceStreamer.java        |   47 +++---
 .../internal/services/ResourceStreamerImpl.java    |   60 ++++----
 .../services/ResponseCompressionAnalyzerImpl.java  |   14 ++-
 .../services/assets/AssetPathConstructorImpl.java  |   69 +++++-----
 .../assets/BaseStreamableResourceImpl.java         |   84 -----------
 .../internal/services/assets/CSSURLRewriter.java   |    6 +-
 .../internal/services/assets/ChecksumPath.java     |   16 +--
 .../assets/ClasspathAssetRequestHandler.java       |    8 +-
 .../assets/CompressedStreamableResource.java       |   16 +--
 .../assets/ContextAssetRequestHandler.java         |    7 +-
 .../assets/JavaScriptStackAssemblerImpl.java       |    4 +-
 .../services/assets/SRSCompressingInterceptor.java |    7 +-
 .../services/assets/StackAssetRequestHandler.java  |   41 +++---
 .../services/assets/StreamableResourceImpl.java    |   69 ++++++++--
 .../JavaScriptStackPathConstructorImpl.java        |   17 ++-
 .../javascript/MOduleAssetRequestHandler.java      |   94 +++++++++++++
 .../services/javascript/ModuleDispatcher.java      |  107 --------------
 .../services/javascript/ModuleManagerImpl.java     |   38 ++++--
 .../tapestry5/internal/util/VirtualResource.java   |    2 +-
 .../org/apache/tapestry5/services/AssetAlias.java  |   32 +++++
 .../services/ClasspathAssetAliasManager.java       |   12 +-
 .../services/ResponseCompressionAnalyzer.java      |   15 ++-
 .../services/assets/AssetPathConstructor.java      |   40 +++---
 .../services/assets/AssetRequestHandler.java       |    6 +-
 .../tapestry5/services/assets/AssetsModule.java    |   21 ++--
 .../services/assets/StreamableResource.java        |    2 +-
 .../services/javascript/JavaScriptModule.java      |   15 +-
 .../ResponseCompressionAnalyzerTest.groovy         |    2 +-
 .../assets/AssetPathConstructorImplTest.groovy     |    4 +-
 .../ModuleAssetRequestHandlerTest.groovy           |    6 +-
 .../javascript/ModuleDispatcherTests.groovy        |    6 +-
 .../ClasspathAssetAliasManagerImplTest.java        |   47 ++----
 .../services/ClasspathAssetFactoryTest.java        |   78 ----------
 .../internal/services/ContextAssetFactoryTest.java |   74 ----------
 .../assets/ContextAssetRequestHandlerTest.java     |    4 +-
 42 files changed, 620 insertions(+), 833 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 8f16694..ccea4a0 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -154,6 +154,8 @@ public class SymbolConstants
      * necessary to GZIP requests, then override this to "false".
      *
      * @see #MIN_GZIP_SIZE
+     * @see org.apache.tapestry5.services.ResponseCompressionAnalyzer
+     * @see org.apache.tapestry5.services.assets.CompressionAnalyzer
      * @since 5.1.0.0
      */
     public static final String GZIP_COMPRESSION_ENABLED = "tapestry.gzip-compression-enabled";
@@ -330,7 +332,7 @@ public class SymbolConstants
     /**
      * Prefix to be used for all resource paths, used to recognize which requests are for assets. This value
      * is appended to the context path and the (optional {@linkplain #APPLICATION_FOLDER application folder}.
-     * Its default is "assets".  It may contain slashes, but should not begin or end with one. This is the prefix
+     * Its default is "asset".  It may contain slashes, but should not begin or end with one. This is the prefix
      * for uncompressed assets.
      */
     public static final String ASSET_PATH_PREFIX = "tapestry.asset-path-prefix";
@@ -338,7 +340,8 @@ public class SymbolConstants
     /**
      * As with {@link #ASSET_PATH_PREFIX} but for compressed versions of assets. At render time, it is determined
      * whether each asset is compressable (for example, image file formats are already compressed). A path for
-     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix suffixed with ".gz".
+     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix
+     * suffixed with ".gz".
      *
      * @since 5.4
      */
@@ -409,4 +412,5 @@ public class SymbolConstants
      * @since 5.4
      */
     public static final String SESSION_LOCKING_ENABLED = "tapestry.session-locking-enabled";
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
index 38e5994..74adf03 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,12 +15,11 @@
 package org.apache.tapestry5;
 
 import org.apache.tapestry5.internal.structure.PageResetListener;
-import org.apache.tapestry5.runtime.PageLifecycleListener;
 import org.apache.tapestry5.services.ComponentEventLinkEncoder;
 
 /**
  * Constants needed by end-user classes.
- * 
+ *
  * @since 5.2.0
  */
 public class TapestryConstants
@@ -40,12 +39,22 @@ public class TapestryConstants
      * notification} after the initialization event; the
      * LOOPBACK
      * prevents this reset notification.
-     * 
-     * @since 5.2.0
+     *
      * @see ComponentEventLinkEncoder#createPageRenderLink(org.apache.tapestry5.services.PageRenderRequestParameters)
      * @see ComponentEventLinkEncoder#decodePageRenderRequest(org.apache.tapestry5.services.Request)
      * @see PageResetListener
+     * @since 5.2.0
      */
     public static final String PAGE_LOOPBACK_PARAMETER_NAME = "t:lb";
 
+    /**
+     * {@link org.apache.tapestry5.services.Request} attribute set to true or false before an
+     * {@link org.apache.tapestry5.services.assets.AssetRequestHandler} is invoked. This information
+     * is used downstream, typically by {@link org.apache.tapestry5.internal.services.ResourceStreamer},
+     * to decide whether to obtain a normal, or compressed, {@link org.apache.tapestry5.services.assets.StreamableResource}.
+     *
+     * @since 5.4
+     */
+    public static final String COMPRESS_CONTENT = "tapestry.compress-content";
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
index 7caff2c..e230999 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
@@ -14,18 +14,96 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
-import org.apache.tapestry5.internal.util.RecomputableSupport;
-import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.AssetFactory;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+import java.io.IOException;
 
 public abstract class AbstractAssetFactory implements AssetFactory
 {
-    protected final RecomputableSupport recomputable = new RecomputableSupport();
+    private final AssetPathConstructor assetPathConstructor;
+
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
+
+    private final StreamableResourceSource streamableResourceSource;
+
+    private final ResourceChangeTracker resourceChangeTracker;
+
+    private final Resource rootResource;
+
+    public AbstractAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                                ResourceChangeTracker resourceChangeTracker,
+                                StreamableResourceSource streamableResourceSource,
+                                AssetPathConstructor assetPathConstructor,
+                                Resource rootResource)
+    {
+        this.compressionAnalyzer = compressionAnalyzer;
+        this.resourceChangeTracker = resourceChangeTracker;
+        this.streamableResourceSource = streamableResourceSource;
+        this.assetPathConstructor = assetPathConstructor;
+        this.rootResource = rootResource;
+    }
+
+    protected boolean isCompressable(StreamableResource resource)
+    {
+        return compressionAnalyzer.isGZipEnabled(resource.getContentType());
+    }
 
-    @PostInjection
-    public void updateVersionNumberOnResourceChange(ResourceChangeTracker tracker)
+    /**
+     * Returns the root {@link ContextResource}.
+     */
+    public Resource getRootResource()
     {
-        recomputable.initialize(tracker);
+        return rootResource;
+    }
+
+    protected Asset createAsset(final Resource resource, final String folder, final String resourcePath)
+    {
+        assert resource != null;
+        assert InternalUtils.isNonBlank(folder);
+        assert InternalUtils.isNonBlank(resourcePath);
+
+        return new AbstractAsset(false)
+        {
+            @Override
+            public String toClientURL()
+            {
+                // TODO: Some caching to ensure this is fast!  Fortunately, the SRS pipeline includes caching already,
+                // but the results of assetPathConstructor could be cached as well, maybe.
+
+                try
+                {
+                    // Get the uncompressed version, so that we can identify its content type (and remember, the extension is not enough,
+                    // as some types get translated to new content types by the SRS).
+
+                    StreamableResource uncompressed = streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_DISABLED, resourceChangeTracker);
+
+                    StreamableResource forRequest = isCompressable(uncompressed)
+                            ? streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_ENABLED, resourceChangeTracker)
+                            : uncompressed;
+
+                    return assetPathConstructor.constructAssetPath(folder, resourcePath, forRequest);
+                } catch (IOException ex)
+                {
+                    throw new RuntimeException(String.format("Unable to construct asset path for %s: %s",
+                            resource, InternalUtils.toMessage(ex)), ex);
+                }
+            }
+
+            @Override
+            public Resource getResource()
+            {
+                return resource;
+            }
+        };
+
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
index 2ab1a68..526c9d5 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.ioc.annotations.Marker;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
@@ -55,6 +56,20 @@ public class AssetDispatcher implements Dispatcher
 
     private final String uncompressedPathPrefix, compressedPathPrefix;
 
+    private AssetRequestHandler wrap(final boolean compress, final AssetRequestHandler handler)
+    {
+        return new AssetRequestHandler()
+        {
+            @Override
+            public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
+            {
+                request.setAttribute(TapestryConstants.COMPRESS_CONTENT, compress);
+
+                return handler.handleAssetRequest(request, response, extraPath);
+            }
+        };
+    }
+
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
@@ -72,8 +87,8 @@ public class AssetDispatcher implements Dispatcher
         {
             AssetRequestHandler handler = configuration.get(path);
 
-            addPath(uncompressedPathPrefix, path, handler);
-            addPath(compressedPathPrefix, path, handler);
+            addPath(uncompressedPathPrefix, path, wrap(false, handler));
+            addPath(compressedPathPrefix, path, wrap(true, handler));
         }
 
         // Sort by descending length
@@ -124,7 +139,6 @@ public class AssetDispatcher implements Dispatcher
 
         for (String extendedPath : assetPaths)
         {
-
             if (path.startsWith(extendedPath))
             {
                 AssetRequestHandler handler = pathToHandler.get(extendedPath);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
index 7a04a87..9c14d6a 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
@@ -16,13 +16,11 @@ package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -30,8 +28,6 @@ import java.util.Map;
 
 public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManager
 {
-    private final AssetPathConstructor assetPathConstructor;
-
     /**
      * Map from alias to path.
      */
@@ -42,25 +38,18 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
      */
     private final Map<String, String> pathPrefixToAlias = CollectionFactory.newMap();
 
-    private final List<String> sortedAliases;
-
     private final List<String> sortedPathPrefixes;
 
     /**
-     * Configuration is a map of aliases (short names) to complete names. Keys and values should end with a slash, but
-     * one will be provided as necessary, so don't both.
+     * Configuration is a map of aliases (short names) to complete names. Keys and values must not start or end with a slash,
+     * but may contain them.
      */
-    public ClasspathAssetAliasManagerImpl(AssetPathConstructor assetPathConstructor,
-
-                                          Map<String, String> configuration)
+    public ClasspathAssetAliasManagerImpl(Map<String, String> configuration)
     {
-        this.assetPathConstructor = assetPathConstructor;
-
         for (Map.Entry<String, String> e : configuration.entrySet())
         {
             String alias = verify("folder name", e.getKey());
 
-
             String path = verify("path", e.getValue());
 
             aliasToPathPrefix.put(alias, path);
@@ -75,9 +64,6 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
             }
         };
 
-        sortedAliases = CollectionFactory.newList(aliasToPathPrefix.keySet());
-        Collections.sort(sortedAliases, sortDescendingByLength);
-
         sortedPathPrefixes = CollectionFactory.newList(aliasToPathPrefix.values());
         Collections.sort(sortedPathPrefixes, sortDescendingByLength);
     }
@@ -93,7 +79,7 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
 
     }
 
-    public String toClientURL(Resource resource)
+    public AssetAlias extractAssetAlias(Resource resource)
     {
         String resourcePath = resource.getPath();
 
@@ -101,18 +87,18 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
         {
             if (resourcePath.startsWith(pathPrefix))
             {
+                // Prevent matching path prefix "foo" against "foobar" ... it must match against "foo/".
+                if (resourcePath.charAt(pathPrefix.length()) != '/')
+                {
+                    continue;
+                }
+
                 String virtualFolder = pathPrefixToAlias.get(pathPrefix);
 
-                String virtualPath = resourcePath.substring(pathPrefix.length() + 1);
+                // Don't want that slash seperating the folder from the rest of the path.
+                String path = resourcePath.substring(pathPrefix.length() + 1);
 
-                try
-                {
-                    return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath, resource);
-                } catch (IOException ex)
-                {
-                    throw new RuntimeException(String.format("Unable to construct asset path for %s/%s (from %s): %s",
-                            virtualFolder, virtualPath, resource, InternalUtils.toMessage(ex)), ex);
-                }
+                return new AssetAlias(virtualFolder, path);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
index 0173058..1b34fd6 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
@@ -15,13 +15,16 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Marker;
 import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
-import org.apache.tapestry5.services.AssetPathConverter;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
 import org.apache.tapestry5.services.ClasspathProvider;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 /**
  * Generates Assets for files on the classpath.
@@ -33,89 +36,23 @@ public class ClasspathAssetFactory extends AbstractAssetFactory
 {
     private final ClasspathAssetAliasManager aliasManager;
 
-    private final ClasspathResource rootResource;
-
-    private final AssetPathConverter converter;
-
-    private final boolean invariant;
-
-    public ClasspathAssetFactory(ClasspathAssetAliasManager aliasManager, AssetPathConverter converter)
+    public ClasspathAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                                 ResourceChangeTracker resourceChangeTracker,
+                                 StreamableResourceSource streamableResourceSource,
+                                 AssetPathConstructor assetPathConstructor,
+                                 ClasspathAssetAliasManager aliasManager)
     {
-        this.aliasManager = aliasManager;
-        this.converter = converter;
-
-        rootResource = new ClasspathResource("");
+        super(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor,
+                new ClasspathResource(""));
 
-        invariant = converter.isInvariant();
-    }
-
-    private String clientURL(Resource resource)
-    {
-        String assetPath = aliasManager.toClientURL(resource);
-
-        return converter.convertAssetPath(assetPath);
+        this.aliasManager = aliasManager;
     }
 
+    @Override
     public Asset createAsset(Resource resource)
     {
-        if (invariant)
-        {
-            return createInvariantAsset(resource);
-        }
-
-        return createVariantAsset(resource);
-    }
-
-    /**
-     * A variant asset must pass the resource through clientURL() all the time; very inefficient.
-     */
-    private Asset createVariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(false)
-        {
-            public Resource getResource()
-            {
-                return resource;
-            }
+        AssetAlias alias = aliasManager.extractAssetAlias(resource);
 
-            public String toClientURL()
-            {
-                return clientURL(resource);
-            }
-        };
-    }
-
-    /**
-     * An invariant asset is normal, and only needs to compute the clientURL for the resource once (
-     * or when the underlying Resource's content has changed).
-     */
-    private Asset createInvariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(true)
-        {
-            private final Invokable<String> clientURL = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return clientURL(resource);
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return clientURL.invoke();
-            }
-        };
-    }
-
-    public Resource getRootResource()
-    {
-        return rootResource;
+        return createAsset(resource, alias.virtualFolder, alias.path);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
index 32d63f7..4731cdf 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
@@ -15,15 +15,13 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.AssetPathConverter;
 import org.apache.tapestry5.services.Context;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
-
-import java.io.IOException;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 /**
  * Implementation of {@link AssetFactory} for assets that are part of the web application context.
@@ -32,102 +30,19 @@ import java.io.IOException;
  */
 public class ContextAssetFactory extends AbstractAssetFactory
 {
-    private final AssetPathConstructor assetPathConstructor;
-
-    private final Resource rootResource;
-
-    private final AssetPathConverter converter;
-
-    private final boolean invariant;
-
-    public ContextAssetFactory(AssetPathConstructor assetPathConstructor, Context context,
-
-                               AssetPathConverter converter)
-    {
-        this.assetPathConstructor = assetPathConstructor;
-        this.converter = converter;
-
-        rootResource = new ContextResource(context, "/");
-        invariant = this.converter.isInvariant();
-    }
 
-    public Asset createAsset(Resource resource)
+    public ContextAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                               ResourceChangeTracker resourceChangeTracker,
+                               StreamableResourceSource streamableResourceSource,
+                               AssetPathConstructor assetPathConstructor,
+                               Context context)
     {
-        if (invariant)
-        {
-            return createInvariantAsset(resource);
-        }
-
-        return createVariantAsset(resource);
-    }
-
-    private String defaultPath(Resource resource)
-    {
-        try
-        {
-            return assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath(), resource);
-        } catch (IOException ex)
-        {
-            throw new RuntimeException(String.format("Unable to construct asset path for %s: %s",
-                    resource, InternalUtils.toMessage(ex)), ex);
-        }
-    }
-
-    private Asset createInvariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(true)
-        {
-            private final Invokable<String> clientURL = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return converter.convertAssetPath(defaultPath(resource));
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return clientURL.invoke();
-            }
-        };
-    }
-
-    private Asset createVariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(false)
-        {
-            private final Invokable<String> defaultPath = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return defaultPath(resource);
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return converter.convertAssetPath(defaultPath.invoke());
-            }
-        };
+        super(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor,
+                new ContextResource(context, "/"));
     }
 
-    /**
-     * Returns the root {@link org.apache.tapestry5.internal.services.ContextResource}.
-     */
-    public Resource getRootResource()
+    public Asset createAsset(final Resource resource)
     {
-        return rootResource;
+        return createAsset(resource, RequestConstants.CONTEXT_FOLDER, resource.getPath());
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
index cf4eefd..81027bf 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2011 The Apache Software Foundation
+// Copyright 2006, 2008, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20,18 +20,22 @@ import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.EnumSet;
 import java.util.Set;
 
 /**
- * Responsible for streaming the contents of a resource to the client. The {@link org.apache.tapestry5.ioc.Resource} to
- * stream is a {@link org.apache.tapestry5.ioc.internal.util.ClasspathResource} or {@link ContextResource}.
+ * Responsible for streaming the contents of a resource to the client. This is sometimes a simple
+ * {@link Resource} (often from the {@link org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler},
+ * or more frequently an asset represented as a {@link StreamableResource} (via {@link AssetDispatcher}, {@link org.apache.tapestry5.services.assets.AssetRequestHandler},
+ * and {@link StreamableResourceSource}). As of 5.4, the ResourceStreamer handles ETag support, as well as
+ * validation of the checksum (provided in the URL).
  *
  * @since 5.1.0.0
  */
 public interface ResourceStreamer
 {
     /**
-     * Used to customize the behavior of {@link #streamResource(org.apache.tapestry5.ioc.Resource, java.util.Set)}.
+     * Used to customize the behavior of {@link #streamResource(org.apache.tapestry5.ioc.Resource, java.lang.String, java.util.Set)}.
      *
      * @since 5.4
      */
@@ -44,42 +48,37 @@ public interface ResourceStreamer
         OMIT_EXPIRATION
     }
 
-    /**
-     * Streams the content of the resource to the client (or sends
-     * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}). Encapsulates logic for compression
-     * and for caching.
-     *
-     * @see StreamableResourceSource
-     */
-    void streamResource(Resource resource) throws IOException;
+    static final Set<Options> DEFAULT_OPTIONS = EnumSet.noneOf(Options.class);
 
     /**
      * Streams the content of the resource to the client (or sends
      * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}). Encapsulates logic for compression
      * and for caching.
      *
-     * @see StreamableResourceSource
-     */
-    void streamResource(Resource resource, Set<Options> options) throws IOException;
-
-    /**
-     * Streams a resource that has been assembled elsewhere, using default options of none.
-     *
      * @param resource
-     * @throws IOException
-     * @since 5.3
+     *         to stream
+     * @param providedChecksum
+     *         checksum from URL (or null to not validate against checksum, which is normal for modules)
+     * @param options
+     *         enable or disable certain features
+     * @see StreamableResourceSource
      */
-    void streamResource(StreamableResource resource) throws IOException;
+    boolean streamResource(Resource resource, String providedChecksum, Set<Options> options) throws IOException;
 
     /**
      * Streams a resource that has been assembled elsewhere.
      *
      * @param resource
      *         content to stream
+     * @param providedChecksum
+     *         checksum provided (in the URL) to validate against the {@linkplain org.apache.tapestry5.services.assets.StreamableResource#getChecksum()} actual checksum}
+     *         for the resource, may be blank to not validate against the checksum
      * @param options
-     *         any special options related to streaming the resource
+     *         enable or disable certain features
+     * @return true if the request was handled (even if sending a {@link HttpServletResponse#SC_NOT_MODIFIED} response),
+     *         or false if the request was not handled (because the provided checksum did not match the actual checksum).
      * @throws IOException
      * @since 5.4
      */
-    void streamResource(StreamableResource resource, Set<Options> options) throws IOException;
+    boolean streamResource(StreamableResource resource, String providedChecksum, Set<Options> options) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
index 6020adc..6b41cc8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.IOOperation;
@@ -23,7 +24,6 @@ import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
@@ -32,37 +32,32 @@ import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.EnumSet;
 import java.util.Set;
 
 public class ResourceStreamerImpl implements ResourceStreamer
 {
     static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
 
+    private static final String QUOTE = "\"";
+
     private final Request request;
 
     private final Response response;
 
     private final StreamableResourceSource streamableResourceSource;
 
-    private final ResponseCompressionAnalyzer analyzer;
-
     private final boolean productionMode;
 
     private final OperationTracker tracker;
 
     private final ResourceChangeTracker resourceChangeTracker;
 
-    private final Set<Options> defaultOptions = EnumSet.noneOf(Options.class);
-
     public ResourceStreamerImpl(Request request,
 
                                 Response response,
 
                                 StreamableResourceSource streamableResourceSource,
 
-                                ResponseCompressionAnalyzer analyzer,
-
                                 OperationTracker tracker,
 
                                 @Symbol(SymbolConstants.PRODUCTION_MODE)
@@ -74,52 +69,53 @@ public class ResourceStreamerImpl implements ResourceStreamer
         this.response = response;
         this.streamableResourceSource = streamableResourceSource;
 
-        this.analyzer = analyzer;
         this.tracker = tracker;
         this.productionMode = productionMode;
         this.resourceChangeTracker = resourceChangeTracker;
     }
 
-    public void streamResource(Resource resource) throws IOException
-    {
-        streamResource(resource, defaultOptions);
-    }
-
-    public void streamResource(final Resource resource, final Set<Options> options) throws IOException
+    public boolean streamResource(final Resource resource, final String providedChecksum, final Set<Options> options) throws IOException
     {
         if (!resource.exists())
         {
+            // TODO: Or should we just return false here and not send back a specific error with the (eventual) 404?
+
             response.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("Unable to locate asset '%s' (the file does not exist).", resource));
-            return;
+
+            return true;
         }
 
-        tracker.perform(String.format("Streaming %s", resource), new IOOperation<Void>()
+        final boolean compress = (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
+
+
+        return tracker.perform(String.format("Streaming %s%s", resource, compress ? " (compressed)" : ""), new IOOperation<Boolean>()
         {
-            public Void perform() throws IOException
+            public Boolean perform() throws IOException
             {
-                StreamableResourceProcessing processing = analyzer.isGZipSupported()
+                StreamableResourceProcessing processing = compress
                         ? StreamableResourceProcessing.COMPRESSION_ENABLED
                         : StreamableResourceProcessing.COMPRESSION_DISABLED;
 
                 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
 
-                streamResource(streamable, options);
-
-                return null;
+                return streamResource(streamable, providedChecksum, options);
             }
         });
     }
 
-    public void streamResource(StreamableResource resource) throws IOException
-    {
-        streamResource(resource, defaultOptions);
-    }
-
-    public void streamResource(StreamableResource streamable, Set<Options> options) throws IOException
+    public boolean streamResource(StreamableResource streamable, String providedChecksum, Set<Options> options) throws IOException
     {
         assert streamable != null;
+        assert providedChecksum != null;
         assert options != null;
 
+        String actualChecksum = streamable.getChecksum();
+
+        if (providedChecksum.length() > 0 && !providedChecksum.equals(actualChecksum))
+        {
+            return false;
+        }
+
         long lastModified = streamable.getLastModified();
 
         long ifModifiedSince = 0;
@@ -137,11 +133,11 @@ public class ResourceStreamerImpl implements ResourceStreamer
         if (ifModifiedSince > 0 && ifModifiedSince >= lastModified)
         {
             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-            return;
+            return true;
         }
 
         // ETag should be surrounded with quotes.
-        String token = "\"" + streamable.getChecksum() + "\"";
+        String token = QUOTE + actualChecksum + QUOTE;
 
         // Even when sending a 304, we want the ETag associated with the request.
         // In most cases (except JavaScript modules), the checksum is also embedded into the URL.
@@ -155,7 +151,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
         if (token.equals(providedToken))
         {
             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-            return;
+            return true;
         }
 
         // Prevent the upstream code from compressing when we don't want to.
@@ -184,6 +180,8 @@ public class ResourceStreamerImpl implements ResourceStreamer
         streamable.streamTo(os);
 
         os.close();
+
+        return true;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
index 7432019..7bea13c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010, 2012 The Apache Software Foundation
+// Copyright 2009-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -27,12 +28,15 @@ public class ResponseCompressionAnalyzerImpl implements ResponseCompressionAnaly
 
     private final boolean gzipCompressionEnabled;
 
+    private final CompressionAnalyzer compressionAnalyzer;
+
     public ResponseCompressionAnalyzerImpl(HttpServletRequest request,
                                            @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
-                                           boolean gzipCompressionEnabled)
+                                           boolean gzipCompressionEnabled, CompressionAnalyzer compressionAnalyzer)
     {
         this.request = request;
         this.gzipCompressionEnabled = gzipCompressionEnabled;
+        this.compressionAnalyzer = compressionAnalyzer;
     }
 
     public boolean isGZipSupported()
@@ -65,4 +69,10 @@ public class ResponseCompressionAnalyzerImpl implements ResponseCompressionAnaly
 
         return false;
     }
+
+    @Override
+    public boolean isGZipEnabled(String contentType)
+    {
+        return isGZipSupported() && compressionAnalyzer.isCompressable(contentType);
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
index 6ba0d72..09e434f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
@@ -15,15 +15,14 @@
 package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.internal.services.RequestConstants;
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.AssetPathConverter;
 import org.apache.tapestry5.services.BaseURLSource;
 import org.apache.tapestry5.services.PathConstructor;
 import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 
 import java.io.IOException;
@@ -32,11 +31,11 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 {
     private final Request request;
 
-    private final String prefix;
+    private final String uncompressedPrefix, compressedPrefix;
 
     private final BaseURLSource baseURLSource;
 
-    private final AssetChecksumGenerator assetChecksumGenerator;
+    private final AssetPathConverter pathConverter;
 
     private final boolean fullyQualified;
 
@@ -47,44 +46,53 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
                                     boolean fullyQualified,
 
                                     @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                                    String assetPathPrefix,
+                                    String uncompressedAssetPrefix,
+
+                                    @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
+                                    String compressedAssetPrefix,
 
                                     PathConstructor pathConstructor,
 
-                                    AssetChecksumGenerator assetChecksumGenerator)
+                                    AssetPathConverter pathConverter)
     {
         this.request = request;
         this.baseURLSource = baseURLSource;
 
         this.fullyQualified = fullyQualified;
-        this.assetChecksumGenerator = assetChecksumGenerator;
+        this.pathConverter = pathConverter;
 
-        prefix = pathConstructor.constructClientPath(assetPathPrefix, "");
+        uncompressedPrefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
+        compressedPrefix = pathConstructor.constructClientPath(compressedAssetPrefix, "");
     }
 
-    public String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException
+    public String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException
     {
-        StringBuilder builder = new StringBuilder();
+        assert InternalUtils.isNonBlank(path);
 
-        if (fullyQualified)
-        {
-            builder.append(baseURLSource.getBaseURL(request.isSecure()));
-        }
+        StringBuilder builder = create(resource.getCompression() == CompressionStatus.COMPRESSED, virtualFolder);
+        builder.append("/");
 
-        builder.append(prefix);  // ends with a slash
+        builder.append(resource.getChecksum());
 
-        builder.append(RequestConstants.STACK_FOLDER).append("/");
-        builder.append(assetChecksumGenerator.generateChecksum(resource)).append("/");
-        builder.append(localeName).append("/");
+        builder.append('/');
         builder.append(path);
 
-        return builder.toString();
+        return finish(builder);
+    }
+
+    public String constructAssetPath(String virtualFolder, boolean compressed)
+    {
+        return finish(create(compressed, virtualFolder));
     }
 
-    public String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException
+    private String finish(StringBuilder builder)
+    {
+        return pathConverter.convertAssetPath(builder.toString());
+    }
+
+    private StringBuilder create(boolean compress, String virtualFolder)
     {
         assert InternalUtils.isNonBlank(virtualFolder);
-        assert path != null;
 
         StringBuilder builder = new StringBuilder();
 
@@ -93,21 +101,8 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
             builder.append(baseURLSource.getBaseURL(request.isSecure()));
         }
 
-        builder.append(prefix);
-        builder.append(virtualFolder);
-        builder.append("/");
-
-        builder.append(assetChecksumGenerator.generateChecksum(resource));
-
-        // TODO: Under what conditions would the path ever be blank? Is this allowed? It may have made sense
-        // in 5.3, to allow access to a folder (to allow the client to build relative URLs), but that
-        // is no longer true in 5.4 because of the checksum in the URL.
-        if (InternalUtils.isNonBlank(path))
-        {
-            builder.append('/');
-            builder.append(path);
-        }
+        builder.append(compress ? compressedPrefix : uncompressedPrefix);
 
-        return builder.toString();
+        return builder.append(virtualFolder);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
deleted file mode 100644
index c30eb33..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services.assets;
-
-import org.apache.tapestry5.services.assets.CompressionStatus;
-import org.apache.tapestry5.services.assets.StreamableResource;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public abstract class BaseStreamableResourceImpl implements StreamableResource
-{
-    protected final String description, contentType;
-
-    protected final CompressionStatus compression;
-
-    protected final long lastModified;
-
-    protected final BytestreamCache bytestreamCache;
-
-    public BaseStreamableResourceImpl(long lastModified, String description, BytestreamCache bytestreamCache, String contentType, CompressionStatus compression)
-    {
-        this.lastModified = lastModified;
-        this.description = description;
-        this.bytestreamCache = bytestreamCache;
-        this.contentType = contentType;
-        this.compression = compression;
-    }
-
-    public String getDescription()
-    {
-        return description;
-    }
-
-    public CompressionStatus getCompression()
-    {
-        return compression;
-    }
-
-    public String getContentType()
-    {
-        return contentType;
-    }
-
-    public int getSize()
-    {
-        return bytestreamCache.size();
-    }
-
-    public long getLastModified()
-    {
-        return lastModified;
-    }
-
-    public void streamTo(OutputStream os) throws IOException
-    {
-        bytestreamCache.writeTo(os);
-    }
-
-    public InputStream openStream() throws IOException
-    {
-        return bytestreamCache.openStream();
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("StreamableResource<%s %s %s lastModified: %tc size: %d>", contentType, description, compression.name(),
-                lastModified, getSize());
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
index 2dec879..fc673fa 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
@@ -34,11 +34,15 @@ import java.util.regex.Pattern;
  * the CSS file and the image assets it may refer to (useful for incorporating a hash of the resource's content into
  * the exposed URL).
  *
+ * <p>
+ * One potential problem with URL rewriting is the way that URLs for referenced resources are generated; we are
+ * somewhat banking on the fact that referenced resources are non-compressable images.
+ *
  * @since 5.4
  */
 public class CSSURLRewriter extends DelegatingSRS
 {
-    // Group 1 is the optional single or double quote
+    // Group 1 is the optional single or double quote (note the use of backtracking to match it)
     // Group 2 is the text inside the quotes, or inside the parens if no quotes
     private final Pattern urlPattern = Pattern.compile("url\\(\\s*(['\"]?)(.+?)\\1\\s*\\)", Pattern.MULTILINE);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
index da6dc77..074fd86 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
@@ -16,7 +16,6 @@ package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 
 import java.io.IOException;
 
@@ -34,12 +33,9 @@ public class ChecksumPath
 
     private final ResourceStreamer streamer;
 
-    private final AssetChecksumGenerator assetChecksumGenerator;
-
-    public ChecksumPath(ResourceStreamer streamer, AssetChecksumGenerator assetChecksumGenerator, String baseFolder, String extraPath)
+    public ChecksumPath(ResourceStreamer streamer, String baseFolder, String extraPath)
     {
         this.streamer = streamer;
-        this.assetChecksumGenerator = assetChecksumGenerator;
         int slashx = extraPath.indexOf('/');
 
         checksum = extraPath.substring(0, slashx);
@@ -67,15 +63,7 @@ public class ChecksumPath
             return false;
         }
 
-        String actualChecksum = assetChecksumGenerator.generateChecksum(resource);
-
-        if (!actualChecksum.equals(checksum))
-        {
-            return false;
-        }
-
-        streamer.streamResource(resource);
 
-        return true;
+        return streamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
index 0a7e39a..b2bdeff 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
@@ -20,7 +20,6 @@ import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
 import java.io.IOException;
@@ -35,24 +34,21 @@ public class ClasspathAssetRequestHandler implements AssetRequestHandler
 {
     private final ResourceStreamer streamer;
 
-    private final AssetChecksumGenerator checksumGenerator;
-
     private final AssetSource assetSource;
 
     private final String baseFolder;
 
     public ClasspathAssetRequestHandler(ResourceStreamer streamer,
-                                        AssetChecksumGenerator checksumGenerator, AssetSource assetSource, String baseFolder)
+                                        AssetSource assetSource, String baseFolder)
     {
         this.streamer = streamer;
-        this.checksumGenerator = checksumGenerator;
         this.assetSource = assetSource;
         this.baseFolder = baseFolder;
     }
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        ChecksumPath path = new ChecksumPath(streamer, checksumGenerator, baseFolder, extraPath);
+        ChecksumPath path = new ChecksumPath(streamer, baseFolder, extraPath);
 
         Resource resource = assetSource.resourceForPath(path.resourcePath);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
index 5f447b4..5dc7ea2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 
@@ -28,22 +29,13 @@ import java.util.zip.GZIPOutputStream;
  *
  * @since 5.4
  */
-public class CompressedStreamableResource extends BaseStreamableResourceImpl
+public class CompressedStreamableResource extends StreamableResourceImpl
 {
-    private final StreamableResource base;
-
-    public String getChecksum() throws IOException
+    public CompressedStreamableResource(StreamableResource base, AssetChecksumGenerator assetChecksumGenerator) throws IOException
     {
-        return base.getChecksum();
-    }
-
-    public CompressedStreamableResource(StreamableResource base) throws IOException
-    {
-        super(base.getLastModified(), base.getDescription(), compressContent(base), base.getContentType(), CompressionStatus.COMPRESSED);
+        super(base.getDescription(), base.getContentType(), CompressionStatus.COMPRESSED, base.getLastModified(), compressContent(base), assetChecksumGenerator);
 
         assert base.getCompression() == CompressionStatus.COMPRESSABLE;
-
-        this.base = base;
     }
 
     private static BytestreamCache compressContent(StreamableResource resource) throws IOException

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
index 72a634c..75a4b6c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
@@ -18,7 +18,6 @@ import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
 import java.io.IOException;
@@ -34,21 +33,19 @@ public class ContextAssetRequestHandler implements AssetRequestHandler
 {
     private final ResourceStreamer resourceStreamer;
 
-    private final AssetChecksumGenerator checksumGenerator;
     private final Resource rootContextResource;
 
     private final Pattern illegal = Pattern.compile("^(((web|meta)-inf.*)|(.*\\.tml$))", Pattern.CASE_INSENSITIVE);
 
-    public ContextAssetRequestHandler(ResourceStreamer resourceStreamer, AssetChecksumGenerator checksumGenerator, Resource rootContextResource)
+    public ContextAssetRequestHandler(ResourceStreamer resourceStreamer, Resource rootContextResource)
     {
         this.resourceStreamer = resourceStreamer;
-        this.checksumGenerator = checksumGenerator;
         this.rootContextResource = rootContextResource;
     }
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        ChecksumPath path = new ChecksumPath(resourceStreamer, checksumGenerator, null, extraPath);
+        ChecksumPath path = new ChecksumPath(resourceStreamer, null, extraPath);
 
         if (illegal.matcher(path.resourcePath).matches())
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
index 92d849a..ece7533 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
@@ -94,7 +94,7 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
         {
             StreamableResource uncompressed = assembleJavascriptResourceForStack(locale, stackName, false);
 
-            return new CompressedStreamableResource(uncompressed);
+            return new CompressedStreamableResource(uncompressed, checksumGenerator);
         }
 
         JavaScriptStack stack = stackSource.getStack(stackName);
@@ -117,7 +117,7 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
         for (Asset library : libraries)
         {
-            String path = library.toClientURL();
+            String path = library.getResource().toString();
 
             paths.put(path);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
index 3781931..f772930 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
@@ -23,10 +23,13 @@ public class SRSCompressingInterceptor extends DelegatingSRS
 {
     private final int compressionCutoff;
 
-    public SRSCompressingInterceptor(StreamableResourceSource delegate, int compressionCutoff)
+    private final AssetChecksumGenerator checksumGenerator;
+
+    public SRSCompressingInterceptor(StreamableResourceSource delegate, int compressionCutoff, AssetChecksumGenerator checksumGenerator)
     {
         super(delegate);
         this.compressionCutoff = compressionCutoff;
+        this.checksumGenerator = checksumGenerator;
     }
 
     public StreamableResource getStreamableResource(Resource baseResource, StreamableResourceProcessing processing, ResourceDependencies dependencies)
@@ -54,6 +57,6 @@ public class SRSCompressingInterceptor extends DelegatingSRS
             return uncompressed;
         }
 
-        return new CompressedStreamableResource(uncompressed);
+        return new CompressedStreamableResource(uncompressed, checksumGenerator);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
index 7edff7f..614cf73 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
@@ -14,13 +14,13 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.IOOperation;
 import org.apache.tapestry5.ioc.OperationTracker;
 import org.apache.tapestry5.services.LocalizationSetter;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.slf4j.Logger;
@@ -35,8 +35,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
 
     private final LocalizationSetter localizationSetter;
 
-    private final ResponseCompressionAnalyzer compressionAnalyzer;
-
     private final ResourceStreamer resourceStreamer;
 
     // Group 1: checksum
@@ -48,18 +46,19 @@ public class StackAssetRequestHandler implements AssetRequestHandler
 
     private final JavaScriptStackAssembler javaScriptStackAssembler;
 
+    private final Request request;
+
     public StackAssetRequestHandler(Logger logger, LocalizationSetter localizationSetter,
-                                    ResponseCompressionAnalyzer compressionAnalyzer,
                                     ResourceStreamer resourceStreamer,
                                     OperationTracker tracker,
-                                    JavaScriptStackAssembler javaScriptStackAssembler)
+                                    JavaScriptStackAssembler javaScriptStackAssembler, Request request)
     {
         this.logger = logger;
         this.localizationSetter = localizationSetter;
-        this.compressionAnalyzer = compressionAnalyzer;
         this.resourceStreamer = resourceStreamer;
         this.tracker = tracker;
         this.javaScriptStackAssembler = javaScriptStackAssembler;
+        this.request = request;
     }
 
     public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
@@ -69,23 +68,12 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                 {
                     public Boolean perform() throws IOException
                     {
-                        boolean compress = compressionAnalyzer.isGZipSupported();
-
-                        StreamableResource resource = getResource(extraPath, compress);
-
-                        if (resource == null)
-                        {
-                            return false;
-                        }
-
-                        resourceStreamer.streamResource(resource);
-
-                        return true;
+                        return streamStackResource(extraPath);
                     }
                 });
     }
 
-    private StreamableResource getResource(String extraPath, final boolean compressed) throws IOException
+    private boolean streamStackResource(String extraPath) throws IOException
     {
         Matcher matcher = pathPattern.matcher(extraPath);
 
@@ -93,7 +81,7 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         {
             logger.warn(String.format("Unable to parse '%s' as an asset stack path", extraPath));
 
-            return null;
+            return false;
         }
 
         String checksum = matcher.group(1);
@@ -112,13 +100,20 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                         {
                             public StreamableResource perform() throws IOException
                             {
+                                boolean compressed =
+                                        (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
+
                                 return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
 
                             }
                         });
 
-        return checksum.equals(resource.getChecksum())
-                ? resource
-                : null;
+
+        if (resource == null)
+        {
+            return false;
+        }
+
+        return resourceStreamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
index fbf8156..6e4e7e7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -22,23 +22,74 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-public class StreamableResourceImpl extends BaseStreamableResourceImpl
+public class StreamableResourceImpl implements StreamableResource
 {
-    private final AssetChecksumGenerator assetChecksumGenerator;
+    protected final String description, contentType;
 
-    public StreamableResourceImpl(String description, String contentType, CompressionStatus compression, long lastModified,
-                                  BytestreamCache bytestreamCache, AssetChecksumGenerator assetChecksumGenerator)
-    {
-        super(lastModified, description, bytestreamCache, contentType, compression);
+    protected final CompressionStatus compression;
+
+    protected final long lastModified;
+
+    protected final BytestreamCache bytestreamCache;
+
+    protected final AssetChecksumGenerator assetChecksumGenerator;
 
+    public StreamableResourceImpl(String description, String contentType, CompressionStatus compression, long lastModified, BytestreamCache bytestreamCache, AssetChecksumGenerator assetChecksumGenerator)
+    {
+        this.lastModified = lastModified;
+        this.description = description;
+        this.bytestreamCache = bytestreamCache;
+        this.contentType = contentType;
+        this.compression = compression;
         this.assetChecksumGenerator = assetChecksumGenerator;
     }
 
+    public String getDescription()
+    {
+        return description;
+    }
+
+    public CompressionStatus getCompression()
+    {
+        return compression;
+    }
+
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+    public int getSize()
+    {
+        return bytestreamCache.size();
+    }
+
+    public long getLastModified()
+    {
+        return lastModified;
+    }
+
+    public void streamTo(OutputStream os) throws IOException
+    {
+        bytestreamCache.writeTo(os);
+    }
+
+    public InputStream openStream() throws IOException
+    {
+        return bytestreamCache.openStream();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("StreamableResource<%s %s %s lastModified: %tc size: %d>", contentType, description, compression.name(),
+                lastModified, getSize());
+    }
+
     public String getChecksum() throws IOException
     {
         // Currently, we rely on AssetChecksumGenerator to manage a cache, but that may be better done
-        // here (but must be threadsafe).
+        // here (but must be threads-afe).
         return assetChecksumGenerator.generateChecksum(this);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index 3ff8c06..1f2ab02 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -18,11 +18,13 @@ import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.internal.services.RequestConstants;
 import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.services.ThreadLocale;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
@@ -41,6 +43,8 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
 
     private final JavaScriptStackAssembler assembler;
 
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
+
     private final boolean combineScripts;
 
     private final Mapper<Asset, String> toPath = new Mapper<Asset, String>()
@@ -54,13 +58,14 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
                                               JavaScriptStackSource javascriptStackSource,
                                               JavaScriptStackAssembler assembler,
-                                              @Symbol(SymbolConstants.COMBINE_SCRIPTS)
-                                              boolean combineScripts)
+                                              ResponseCompressionAnalyzer compressionAnalyzer, @Symbol(SymbolConstants.COMBINE_SCRIPTS)
+    boolean combineScripts)
     {
         this.threadLocale = threadLocale;
         this.assetPathConstructor = assetPathConstructor;
         this.javascriptStackSource = javascriptStackSource;
         this.assembler = assembler;
+        this.compressionAnalyzer = compressionAnalyzer;
         this.combineScripts = combineScripts;
     }
 
@@ -89,11 +94,13 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     {
         try
         {
-            StreamableResource streamable = assembler.assembleJavaScriptResourceForStack(stackName, false);
+            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported());
 
-            String path = stackName + ".js";
+            String path = String.format("%s/%s.js",
+                    threadLocale.getLocale(),
+                    stackName);
 
-            String stackURL = assetPathConstructor.constructStackAssetPath(threadLocale.getLocale().toString(), path, streamable);
+            String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, assembled);
 
             return CollectionFactory.newList(stackURL);
         } catch (IOException ex)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
new file mode 100644
index 0000000..d809556
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
@@ -0,0 +1,94 @@
+// Copyright 2012, 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.javascript;
+
+import org.apache.tapestry5.internal.services.AssetDispatcher;
+import org.apache.tapestry5.internal.services.ResourceStreamer;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
+import org.apache.tapestry5.services.javascript.ModuleManager;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name,
+ * and searches for the corresponding JavaScript module.  Unlike normal assets, modules do not include any kind of checksum
+ * in the URL, and do not set a far-future expires header.
+ *
+ * @see ModuleManager
+ */
+public class ModuleAssetRequestHandler implements AssetRequestHandler
+{
+    private final ModuleManager moduleManager;
+
+    private final ResourceStreamer streamer;
+
+    private final OperationTracker tracker;
+
+    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
+
+    public ModuleAssetRequestHandler(ModuleManager moduleManager,
+                                     ResourceStreamer streamer,
+                                     OperationTracker tracker)
+    {
+        this.moduleManager = moduleManager;
+        this.streamer = streamer;
+        this.tracker = tracker;
+    }
+
+    @Override
+    public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
+    {
+        // Ensure request ends with '.js'.  That's the extension tacked on by RequireJS because it expects there
+        // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to
+        // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath
+        // as a module.
+
+        int dotx = extraPath.lastIndexOf('.');
+
+        if (dotx < 0)
+        {
+            return false;
+        }
+
+        if (!extraPath.substring(dotx + 1).equals("js"))
+        {
+            return false;
+        }
+
+        final String moduleName = extraPath.substring(0, dotx);
+
+        return tracker.perform(String.format("Streaming module %s", extraPath), new IOOperation<Boolean>()
+        {
+            public Boolean perform() throws IOException
+            {
+                Resource resource = moduleManager.findResourceForModule(moduleName);
+
+                if (resource != null)
+                {
+                    return streamer.streamResource(resource, "", omitExpiration);
+                }
+
+                return false;
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
deleted file mode 100644
index 85e2279..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2012, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services.javascript;
-
-import org.apache.tapestry5.internal.services.AssetDispatcher;
-import org.apache.tapestry5.internal.services.ResourceStreamer;
-import org.apache.tapestry5.ioc.IOOperation;
-import org.apache.tapestry5.ioc.OperationTracker;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.Dispatcher;
-import org.apache.tapestry5.services.PathConstructor;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.javascript.ModuleManager;
-
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name,
- * and searches for the corresponding JavaScript module.
- */
-public class ModuleDispatcher implements Dispatcher
-{
-    private final ModuleManager moduleManager;
-
-    private final ResourceStreamer streamer;
-
-    private final OperationTracker tracker;
-
-    private final String prefix;
-
-    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
-
-    public ModuleDispatcher(ModuleManager moduleManager,
-                            ResourceStreamer streamer,
-                            PathConstructor pathConstructor,
-                            OperationTracker tracker)
-    {
-        this.moduleManager = moduleManager;
-        this.streamer = streamer;
-        this.tracker = tracker;
-
-        prefix = pathConstructor.constructDispatchPath("modules", "");
-    }
-
-    public boolean dispatch(Request request, Response response) throws IOException
-    {
-        String requestPath = request.getPath();
-
-        if (!requestPath.startsWith(prefix))
-        {
-            return false;
-        }
-
-        String extraPath = requestPath.substring(prefix.length());
-
-        // Ensure request ends with '.js'.  That's the extension tacked on by RequireJS because it expects there
-        // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to
-        // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath
-        // as a module.
-
-        int dotx = extraPath.lastIndexOf('.');
-
-        if (dotx < 0)
-        {
-            return false;
-        }
-
-        if (!extraPath.substring(dotx + 1).equals("js"))
-        {
-            return false;
-        }
-
-        final String moduleName = extraPath.substring(0, dotx);
-
-        return tracker.perform(String.format("Streaming module %s", extraPath), new IOOperation<Boolean>()
-        {
-            public Boolean perform() throws IOException
-            {
-                Resource resource = moduleManager.findResourceForModule(moduleName);
-
-                if (resource != null)
-                {
-                    streamer.streamResource(resource, omitExpiration);
-
-                    return true;
-                }
-
-                return false;
-            }
-        });
-    }
-}


[07/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/05477072
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/05477072
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/05477072

Branch: refs/heads/master
Commit: 05477072442e72550e2acb59c93f420140103a7f
Parents: be3e581
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 22:01:02 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../ModuleAssetRequestHandlerTests.groovy          |   43 ++++++++++
 .../javascript/ModuleDispatcherTests.groovy        |   65 ---------------
 2 files changed, 43 insertions(+), 65 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/05477072/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
new file mode 100644
index 0000000..5b24b1c
--- /dev/null
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
@@ -0,0 +1,43 @@
+package org.apache.tapestry5.services.javascript
+
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
+import org.apache.tapestry5.ioc.internal.QuietOperationTracker
+import org.apache.tapestry5.ioc.test.TestBase
+import org.testng.annotations.DataProvider
+import org.testng.annotations.Test
+
+class ModuleAssetRequestHandlerTests extends TestBase {
+
+    @Test(dataProvider = "unknownPaths")
+    void "invalid extension is ignored"(extraPath) {
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
+
+        assert handler.handleAssetRequest(null, null, extraPath) == false
+    }
+
+    @DataProvider
+    Object[][] unknownPaths() {
+        [
+            "foo/bar.xyz",
+            "foo",
+            "foo/bar",
+            ""
+        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
+    }
+
+    @Test
+    void "returns false if no module is found"() {
+
+        def manager = newMock ModuleManager
+
+        expect(manager.findResourceForModule("foo/bar")).andReturn null
+
+        replay()
+
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
+
+        assert handler.handleAssetRequest(null, null, "foo/bar.js") == false
+
+        verify()
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/05477072/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
deleted file mode 100644
index ca72a21..0000000
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.apache.tapestry5.services.javascript
-
-import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
-import org.apache.tapestry5.ioc.internal.QuietOperationTracker
-import org.apache.tapestry5.ioc.test.TestBase
-import org.apache.tapestry5.services.PathConstructor
-import org.apache.tapestry5.services.Request
-import org.testng.annotations.DataProvider
-import org.testng.annotations.Test
-
-class ModuleDispatcherTests extends TestBase {
-
-    @Test(dataProvider = "unknownPaths")
-    void "invalid extension is ignored"(path) {
-        def pc = newMock PathConstructor
-
-        def request = newMock Request
-
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        expect(request.path).andReturn path
-
-        replay()
-
-        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
-
-        assertEquals handler.dispatch(request, null), false
-
-        verify()
-    }
-
-    @DataProvider
-    Object[][] unknownPaths() {
-        [
-            "foo/bar.xyz",
-            "foo",
-            "foo/bar",
-            ""
-        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
-    }
-
-    @Test
-    void "returns false if no module is found"() {
-
-        def pc = newMock PathConstructor
-
-        def manager = newMock ModuleManager
-
-        def request = newMock Request
-
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        expect(request.path).andReturn("/modules/foo/bar.js")
-
-        expect(manager.findResourceForModule("foo/bar")).andReturn null
-
-        replay()
-
-        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
-
-        assertEquals handler.dispatch(request, null), false
-
-        verify()
-    }
-}


[06/10] git commit: Modify AssetDispatcher to handle two path prefixes: normal and compressed

Posted by hl...@apache.org.
Modify AssetDispatcher to handle two path prefixes: normal and compressed


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/aa465fcd
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/aa465fcd
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/aa465fcd

Branch: refs/heads/master
Commit: aa465fcd3b4b735da9682f377e43553110303494
Parents: 6fd011e
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sat Apr 13 08:25:00 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../src/main/java/org/apache/tapestry5/Asset.java  |    5 ++
 .../java/org/apache/tapestry5/SymbolConstants.java |   12 +++-
 .../internal/services/AssetDispatcher.java         |   50 +++++++++++----
 .../tapestry5/services/assets/AssetsModule.java    |    1 +
 4 files changed, 55 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java b/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
index b2c76ac..a7a9f25 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
@@ -40,8 +40,13 @@ public interface Asset
      * <p/>Note that starting in Tapestry 5.4, it is expected that Asset instances recognize
      * when the underlying Resource's content has changed, and update the clientURL to reflect the new content's
      * checksum. This wasn't an issue in earlier releases where the clientURL incorporated a version number.
+     * <p/>
+     * Finally, starting in 5.4, this value will often be <em>variant</em>: the exact URL returned will depend on
+     * whether the underlying resource content is compressable, whether the current {@link org.apache.tapestry5.services.Request}
+     * supports compression.
      *
      * @see org.apache.tapestry5.services.AssetSource
+     * @see org.apache.tapestry5.services.AssetPathConverter
      */
     String toClientURL();
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 0d671c4..8f16694 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -330,11 +330,21 @@ public class SymbolConstants
     /**
      * Prefix to be used for all resource paths, used to recognize which requests are for assets. This value
      * is appended to the context path and the (optional {@linkplain #APPLICATION_FOLDER application folder}.
-     * Its default is "assets".  It may contain slashes, but should not begin or end with one.
+     * Its default is "assets".  It may contain slashes, but should not begin or end with one. This is the prefix
+     * for uncompressed assets.
      */
     public static final String ASSET_PATH_PREFIX = "tapestry.asset-path-prefix";
 
     /**
+     * As with {@link #ASSET_PATH_PREFIX} but for compressed versions of assets. At render time, it is determined
+     * whether each asset is compressable (for example, image file formats are already compressed). A path for
+     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix suffixed with ".gz".
+     *
+     * @since 5.4
+     */
+    public static final String COMPRESSED_ASSET_PATH_PREFIX = "tapestry.compressed-asset-path-prefix";
+
+    /**
      * The Asset path to the copy of Require JS to include in rendered pages. Any page that makes use of any JavaScript will
      * automatically import this library (as well as the core JavaScript stack).
      *

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
index 4f8e8a7..2ab1a68 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
@@ -30,8 +30,10 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * Recognizes requests where the path begins with "/asset/" and delivers the content therein as a bytestream. Also
- * handles requests that are simply polling for a change to the file.
+ * Recognizes requests where the path begins with "/asset/" (actually, as defined by the
+ * {@link SymbolConstants#ASSET_PATH_PREFIX} symbol), and delivers the content therein as a bytestream. Also
+ * handles requests that are simply polling for a change to the file (including checking the ETag in the
+ * request against {@linkplain org.apache.tapestry5.services.assets.StreamableResource#getChecksum() the asset's checksum}.
  *
  * @see ResourceStreamer
  * @see ClasspathAssetAliasManager
@@ -51,26 +53,27 @@ public class AssetDispatcher implements Dispatcher
      */
     private final List<String> assetPaths = CollectionFactory.newList();
 
-    private final String pathPrefix;
+    private final String uncompressedPathPrefix, compressedPathPrefix;
 
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
 
                            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                           String assetPathPrefix)
+                           String uncompressedPrefix,
+
+                           @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
+                           String compressedPrefix)
     {
-        pathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
+        uncompressedPathPrefix = pathConstructor.constructDispatchPath(uncompressedPrefix, "");
+        compressedPathPrefix = pathConstructor.constructDispatchPath(compressedPrefix, "");
 
         for (String path : configuration.keySet())
         {
-            String extendedPath = path.length() == 0
-                    ? pathPrefix
-                    : pathPrefix + path + "/";
-
-            pathToHandler.put(extendedPath, configuration.get(path));
+            AssetRequestHandler handler = configuration.get(path);
 
-            assetPaths.add(extendedPath);
+            addPath(uncompressedPathPrefix, path, handler);
+            addPath(compressedPathPrefix, path, handler);
         }
 
         // Sort by descending length
@@ -84,6 +87,29 @@ public class AssetDispatcher implements Dispatcher
         });
     }
 
+    private void addPath(String prefix, String path, AssetRequestHandler handler)
+    {
+        String extendedPath = buildPath(prefix, path);
+
+        pathToHandler.put(extendedPath, handler);
+
+        assetPaths.add(extendedPath);
+    }
+
+    private String buildPath(String prefix, String path)
+    {
+        // TODO: Not sure when path would be length 0!
+        return path.length() == 0
+                ? prefix
+                : prefix + path + "/";
+    }
+
+    private boolean matchesEitherPrefix(String path)
+    {
+        return path.startsWith(compressedPathPrefix) ||
+                path.startsWith(uncompressedPathPrefix);
+    }
+
     public boolean dispatch(Request request, Response response) throws IOException
     {
         String path = request.getPath();
@@ -91,7 +117,7 @@ public class AssetDispatcher implements Dispatcher
         // Remember that the request path does not include the context path, so we can simply start
         // looking for the asset path prefix right off the bat.
 
-        if (!path.startsWith(pathPrefix))
+        if (!matchesEitherPrefix(path))
         {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index ad954c0..797e464 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -76,6 +76,7 @@ public class AssetsModule
         configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
 
         configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
+        configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
     }
 
     // The use of decorators is to allow third-parties to get their own extensions


[04/10] URLs for compressed assets are now distinct from uncompressed assets

Posted by hl...@apache.org.
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
index 4bf9597..95642ce 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
@@ -28,7 +28,8 @@ import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONLiteral;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.AssetSource;
-import org.apache.tapestry5.services.PathConstructor;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration;
 import org.apache.tapestry5.services.javascript.ModuleManager;
@@ -39,10 +40,13 @@ import java.util.Set;
 
 public class ModuleManagerImpl implements ModuleManager
 {
-    private final String requireConfig;
+
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
 
     private final Asset requireJS;
 
+    private final Map<String, JavaScriptModuleConfiguration> configuration;
+
     private final Messages globalMessages;
 
     private final boolean compactJSON;
@@ -56,7 +60,11 @@ public class ModuleManagerImpl implements ModuleManager
     // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null.
     private final Map<String, Resource> cache = CollectionFactory.newConcurrentMap();
 
-    public ModuleManagerImpl(PathConstructor constructor,
+    private final boolean devMode;
+
+    private final AssetPathConstructor assetPathConstructor;
+
+    public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer,
                              AssetSource assetSource,
                              @Path("${" + SymbolConstants.REQUIRE_JS + "}")
                              Asset requireJS,
@@ -66,23 +74,28 @@ public class ModuleManagerImpl implements ModuleManager
                              @Symbol(SymbolConstants.COMPACT_JSON)
                              boolean compactJSON,
                              @Symbol(SymbolConstants.PRODUCTION_MODE)
-                             boolean productionMode)
+                             boolean productionMode,
+                             AssetPathConstructor assetPathConstructor)
     {
+        this.compressionAnalyzer = compressionAnalyzer;
         this.requireJS = requireJS;
+        this.configuration = configuration;
         this.globalMessages = globalMessages;
         this.compactJSON = compactJSON;
+        this.assetPathConstructor = assetPathConstructor;
 
-        this.requireConfig = buildRequireJSConfig(constructor.constructClientPath("modules", ""), configuration, !productionMode);
+        this.devMode = !productionMode;
 
         classpathRoot = assetSource.resourceForPath("");
-
         extensions = CollectionFactory.newSet("js");
 
         extensions.addAll(streamableResourceSource.fileExtensionsForContentType("text/javascript"));
     }
 
-    private String buildRequireJSConfig(String baseURL, Map<String, JavaScriptModuleConfiguration> configuration, boolean devMode)
+    private String buildRequireJSConfig()
     {
+        String baseURL = getBaseURL();
+
         JSONObject config = new JSONObject("baseUrl", baseURL);
 
         // In DevMode, wait up to five minutes for a script, as the developer may be using the debugger.
@@ -110,6 +123,11 @@ public class ModuleManagerImpl implements ModuleManager
         return String.format("requirejs.config(%s);\n", config.toString(compactJSON));
     }
 
+    private String getBaseURL()
+    {
+        return assetPathConstructor.constructAssetPath("module", compressionAnalyzer.isGZipSupported());
+    }
+
     private void addModuleToConfig(JSONObject config, String name, JavaScriptModuleConfiguration module)
     {
         JSONObject shimConfig = config.in("shim");
@@ -159,7 +177,10 @@ public class ModuleManagerImpl implements ModuleManager
 
         Element element = body.element("script", "type", "text/javascript");
 
-        element.raw(requireConfig);
+        // Build it each time because we don't know if the client supports GZip or not, and
+        // (in development mode) URLs for some referenced assets could change (due to URLs
+        // containing a checksum on the resource content).
+        element.raw(buildRequireJSConfig());
 
         StringBuilder content = new StringBuilder(1000);
 
@@ -199,7 +220,6 @@ public class ModuleManagerImpl implements ModuleManager
             return resource;
         }
 
-
         // Tack on a fake extension; otherwise modules whose name includes a '.' get mangled
         // by Resource.withExtension().
         String baseName = String.format("/META-INF/modules/%s.EXT", moduleName);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
index 0fc0738..bb1061f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/VirtualResource.java
@@ -30,7 +30,7 @@ import java.util.Locale;
  * the contents of the virtual resource.
  *
  * @see org.apache.tapestry5.services.javascript.ModuleManager
- * @see org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+ * @see org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
  * @since 5.4
  */
 public abstract class VirtualResource implements Resource

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
new file mode 100644
index 0000000..15ee1b7
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/AssetAlias.java
@@ -0,0 +1,32 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http:#www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.services;
+
+/**
+ * Identifies a virtual foldre and a path within that folder.
+ *
+ * @since 5.4
+ * @deprecated Deprecated in 5.4 (see notes in {@link ClasspathAssetAliasManager}).
+ */
+public class AssetAlias
+{
+    public final String virtualFolder, path;
+
+    public AssetAlias(String virtualFolder, String path)
+    {
+        this.virtualFolder = virtualFolder;
+        this.path = path;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
index e85234e..b540780 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
@@ -41,22 +41,22 @@ import java.util.Map;
  * <p/>
  * Tapestry automatically contributes a number of mappings: for the application root package itself (as alias "app") and
  * for each library (via {@link ComponentClassResolver#getFolderToPackageMapping()});
+ *
+ * @deprecated Deprecated in 5.4, with no replacement. This will no longer be used in Tapestry 5.5, as all classpath assets
+ *             will need to be under the {@code META-INF/assets} folder (but may be maintained for compatibility reasons until 5.6).
  */
 @UsesMappedConfiguration(String.class)
 public interface ClasspathAssetAliasManager
 {
     /**
-     * Takes a resource path to a classpath resource and adds the asset path prefix to the path. May also convert part
-     * of the path to an alias (based on the manager's configuration).
-     * <p/>
-     * Note: this method's signature changed incompatibly in Tapestry 5.4.
+     * Takes a classpath resource and determines the proper alias for it based on the mappings contributed to the service.
      *
      * @param resource
      *         classpath resource
      * @return URL ready to send to the client
      */
-    @IncompatibleChange(release = "5.4", details = "parameter changed from String to Resource")
-    String toClientURL(Resource resource);
+    @IncompatibleChange(release = "5.4", details = "parameter changed from String to Resource, renamed from toClientURL() to better identify purpose")
+    AssetAlias extractAssetAlias(Resource resource);
 
     /**
      * Returns the mappings used by the service: the keys are the folder aliases (i.e, "corelib")

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
index ffe0239..d9a61b2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
@@ -1,4 +1,4 @@
-// Copyright 2009, 2011, 2012 The Apache Software Foundation
+// Copyright 2009-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@ package org.apache.tapestry5.services;
 import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 /**
- * Used to determine if the client supports GZIP compression of the response.
+ * Used to determine if the client supports GZip compression of the response.
  *
  * @see CompressionAnalyzer
+ * @see org.apache.tapestry5.SymbolConstants#GZIP_COMPRESSION_ENABLED
  * @since 5.1.0.0
  */
 public interface ResponseCompressionAnalyzer
@@ -31,4 +32,14 @@ public interface ResponseCompressionAnalyzer
      * @return true if gzip is supported by client
      */
     boolean isGZipSupported();
+
+    /**
+     * Uses {@link CompressionAnalyzer} to determine if the content is compressable, but only if the request
+     * indicates the client supports compression.
+     *
+     * @param contentType
+     * @return true if the content can be compressed for the current request
+     * @since 5.4
+     */
+    boolean isGZipEnabled(String contentType);
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
index 17fb2b7..ffb4d2c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
@@ -14,14 +14,13 @@
 
 package org.apache.tapestry5.services.assets;
 
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
 
 import java.io.IOException;
 
 /**
- * Encapsulates the logic or creating the path portion of an asset URL, including
- * the application version.
+ * Encapsulates the logic or creating the path portion of an asset URL, including hooking the {@link org.apache.tapestry5.services.AssetPathConverter}
+ * into the generation.
  *
  * @see org.apache.tapestry5.services.PathConstructor
  * @since 5.2.0
@@ -30,31 +29,38 @@ public interface AssetPathConstructor
 {
     /**
      * Constructs an asset URL path from the virtual folder and path (within the virtual folder).
+     * After constructing the string (and honoring the {@link org.apache.tapestry5.SymbolConstants#ASSET_URL_FULL_QUALIFIED}
+     * symbol), the result is passed through the {@link org.apache.tapestry5.services.AssetPathConverter}.
      *
      * @param virtualFolder
      *         corresponds to a {@link AssetRequestHandler} contributed to the AssetDispatcher service
      * @param path
-     *         within the virtual folder (should <em>not</em> start with a slash). May be the empty string.
-     *         When non-blank, separated from the rest of the path with a slash.
+     *         a path that can be used to identify the underlying {@link org.apache.tapestry5.ioc.Resource} or
+     *         or re-acquire the {@link StreamableResource}; this will be the final portion of the URL, after
+     *         the appropriate prefix (based on whether the resource is compressed or not) and the checksum for the
+     *         resource
      * @param resource
-     *         underlying resource for the asset path, used to compute checksums (since 5.4)
+     *         underlying resource for the asset path; the checksum portion of the URL is obtained from the resource
      * @return path portion of asset URL, which is everything needed by the {@link org.apache.tapestry5.internal.services.AssetDispatcher}
      *         to find and stream the resource
+     * @see StreamableResourceSource
      */
-    @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may not be thrown")
-    String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException;
+    @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may now be thrown")
+    String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException;
 
     /**
-     * Constructs an asset path for a aggregated {@linkplain org.apache.tapestry5.services.javascript.JavaScriptStack stack}.
+     * Generates a base URL for a virtual folder (this exists mostly for {@link org.apache.tapestry5.services.javascript.ModuleManager}
+     * and {@link org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler}). Uses much of the same logic
+     * as {@link #constructAssetPath(String, String, StreamableResource)}, including
+     * {@link org.apache.tapestry5.SymbolConstants#ASSET_URL_FULL_QUALIFIED} and the {@link org.apache.tapestry5.services.AssetPathConverter}.
      *
-     * @param localeName
-     *         name of the locale
-     * @param path
-     *         based on the name of the core stack
-     * @param resource
-     *         the aggregated stack (used when generating the checksum)
-     * @return path that identifies the checksum, locale, and path
+     * @param virtualFolder
+     *         folder that will be used to select a {@link }
+     * @param compressed
+     *         build a path that indicates GZip compression
+     * @return complete path
      * @since 5.4
      */
-    String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException;
+    String constructAssetPath(String virtualFolder, boolean compressed);
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
index c0acce3..e1eb7ed 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
@@ -49,6 +49,9 @@ public interface AssetRequestHandler
      * The handler should return true if it provided a response. If the handler returns false, this indicates that the
      * extra path did not identify a known asset (virtual or otherwise) and the AssetDispatcher service should send a
      * {@link HttpServletResponse#SC_NOT_FOUND} response.
+     * <p/>
+     * Starting in Tapestry 5.4, the handler is informed by the {@link org.apache.tapestry5.services.AssetRequestDispatcher}
+     * whether or not the content should be compressed (this is determined based on information in the URL).
      *
      * @param request
      *         incoming asset request
@@ -56,7 +59,8 @@ public interface AssetRequestHandler
      *         used to send a response to client
      * @param extraPath
      *         additional path to identify the specific asset
-     * @return true if request handler, false if asset not found
+     * @return true if request was handled (and response sent), false if asset not found
+     * @see org.apache.tapestry5.TapestryConstants#COMPRESS_CONTENT
      */
     boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index 797e464..d727792 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -75,7 +75,7 @@ public class AssetsModule
         configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
         configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
 
-        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
+        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "asset");
         configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
     }
 
@@ -86,10 +86,11 @@ public class AssetsModule
     public StreamableResourceSource enableCompression(StreamableResourceSource delegate,
                                                       @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
                                                       boolean gzipEnabled, @Symbol(SymbolConstants.MIN_GZIP_SIZE)
-                                                      int compressionCutoff)
+                                                      int compressionCutoff,
+                                                      AssetChecksumGenerator checksumGenerator)
     {
         return gzipEnabled
-                ? new SRSCompressingInterceptor(delegate, compressionCutoff)
+                ? new SRSCompressingInterceptor(delegate, compressionCutoff, checksumGenerator)
                 : null;
     }
 
@@ -189,12 +190,12 @@ public class AssetsModule
 
     @Marker(ContextProvider.class)
     public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
-
                                                         AssetPathConstructor assetPathConstructor,
-
-                                                        AssetPathConverter converter)
+                                                        ResponseCompressionAnalyzer compressionAnalyzer,
+                                                        ResourceChangeTracker resourceChangeTracker,
+                                                        StreamableResourceSource streamableResourceSource)
     {
-        return new ContextAssetFactory(assetPathConstructor, globals.getContext(), converter);
+        return new ContextAssetFactory(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor, globals.getContext());
     }
 
     @Contribute(ClasspathAssetAliasManager.class)
@@ -222,8 +223,6 @@ public class AssetsModule
                                                       @Autobuild
                                                       StackAssetRequestHandler stackAssetRequestHandler,
 
-                                                      AssetChecksumGenerator assetChecksumGenerator,
-
                                                       ClasspathAssetAliasManager classpathAssetAliasManager,
                                                       ResourceStreamer streamer,
                                                       AssetSource assetSource)
@@ -234,11 +233,11 @@ public class AssetsModule
         {
             String path = mappings.get(folder);
 
-            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetChecksumGenerator, assetSource, path));
+            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetSource, path));
         }
 
         configuration.add(RequestConstants.CONTEXT_FOLDER,
-                new ContextAssetRequestHandler(streamer, assetChecksumGenerator, contextAssetFactory.getRootResource()));
+                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
 
         configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
index 3edeff5..ab6cd16 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
@@ -31,7 +31,7 @@ import java.io.OutputStream;
 public interface StreamableResource
 {
     /**
-     * Describes the underlying {@link Resource} (or resources} for this streamble resource; expressly used
+     * Describes the underlying {@link Resource} (or resources} for this streamable resource; expressly used
      * as part of the object's {@code toString()}.
      */
     String getDescription();

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
index cd19a5e..39417a3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
@@ -28,13 +28,13 @@ import org.apache.tapestry5.ioc.OrderedConfiguration;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
-import org.apache.tapestry5.ioc.annotations.Primary;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.FactoryDefaults;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.compatibility.Compatibility;
 import org.apache.tapestry5.services.compatibility.Trait;
 import org.apache.tapestry5.services.messages.ComponentMessagesSource;
@@ -130,6 +130,13 @@ public class JavaScriptModule
         return environmentalBuilder.build(JavaScriptSupport.class);
     }
 
+    @Contribute(Dispatcher.class)
+    @AssetRequestDispatcher
+    public static void provideModuleHandler(MappedConfiguration<String, AssetRequestHandler> configuration)
+    {
+        configuration.addInstance("module", ModuleAssetRequestHandler.class);
+    }
+
     /**
      * Adds page render filters, each of which provides an {@link org.apache.tapestry5.annotations.Environmental}
      * service. Filters
@@ -210,12 +217,6 @@ public class JavaScriptModule
         configuration.add("JavaScriptSupport", javascriptSupport, "after:DocumentLinker");
     }
 
-    @Contribute(Dispatcher.class)
-    @Primary
-    public static void handleModuleRequests(OrderedConfiguration<Dispatcher> configuration)
-    {
-        configuration.addInstance("Module", ModuleDispatcher.class, "before:PageRender");
-    }
 
     @Contribute(ModuleManager.class)
     public static void setupBaseModules(MappedConfiguration<String, Object> configuration,

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
index 3b2f4c1..f702e2e 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerTest.groovy
@@ -17,7 +17,7 @@ class ResponseCompressionAnalyzerTest extends TestBase {
 
     replay()
 
-    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true)
+    ResponseCompressionAnalyzer rca = new ResponseCompressionAnalyzerImpl(request, true, compressionAnalyzer)
 
     assert rca.isGZipSupported() == false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
index 7e41fbf..9065587 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
@@ -31,7 +31,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
     replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", pc, gen)
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", null, pc, pathConverter)
 
     assert apc.constructAssetPath("virt", "extra.png", virtExtra) == "/assets/virt/abc/extra.png"
 
@@ -58,7 +58,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
     replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", pc, gen)
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", null, pc, pathConverter)
 
     assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/911/icon.gif"
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
index 3c974b7..56635c3 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
@@ -1,6 +1,6 @@
 package org.apache.tapestry5.services.javascript
 
-import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.PathConstructor
@@ -25,7 +25,7 @@ class ModuleAssetRequestHandlerTest extends TestBase {
 
         replay()
         
-        def handler = new ModuleDispatcher(null, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
         
         assertEquals handler.dispatch(request, response), false
 
@@ -61,7 +61,7 @@ class ModuleAssetRequestHandlerTest extends TestBase {
         
         replay()
 
-        def handler = new ModuleDispatcher(manager, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
         
         assertEquals handler.dispatch(request, response), false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
index 35b407a..ca72a21 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
@@ -1,6 +1,6 @@
 package org.apache.tapestry5.services.javascript
 
-import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.PathConstructor
@@ -22,7 +22,7 @@ class ModuleDispatcherTests extends TestBase {
 
         replay()
 
-        def handler = new ModuleDispatcher(null, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
 
         assertEquals handler.dispatch(request, null), false
 
@@ -56,7 +56,7 @@ class ModuleDispatcherTests extends TestBase {
 
         replay()
 
-        def handler = new ModuleDispatcher(manager, null, pc, new QuietOperationTracker())
+        def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
 
         assertEquals handler.dispatch(request, null), false
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
index 776ad50..fcff983 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
@@ -14,17 +14,12 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
-import org.apache.tapestry5.services.BaseURLSource;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.apache.tapestry5.services.PathConstructor;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -33,8 +28,6 @@ import java.util.Map;
 
 public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 {
-    private static final String APP_VERSION = "1.2.3";
-
     public Map<String, String> configuration()
     {
         Map<String, String> configuration = CollectionFactory.newMap();
@@ -55,7 +48,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 
         try
         {
-            new ClasspathAssetAliasManagerImpl(null, configuration);
+            new ClasspathAssetAliasManagerImpl(configuration);
             unreachable();
         } catch (RuntimeException ex)
         {
@@ -75,34 +68,26 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
         expected.put("tapestry-internal", "org/apache/tapestry5/internal");
         expected.put("mylib", "com/example/mylib");
 
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(null, configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
 
         assertEquals(manager.getMappings(), expected);
     }
 
     @Test(dataProvider = "to_client_url_data")
-    public void to_client_url(String resourcePath, String expectedClientURL) throws IOException
+    public void to_client_url(String resourcePath, String expectedFolder, String expectedPath) throws IOException
     {
-        Request request = mockRequest();
         Resource r = mockResource();
 
         expect(r.getPath()).andReturn(resourcePath);
 
-        BaseURLSource baseURLSource = newMock(BaseURLSource.class);
-        PathConstructor pc = newMock(PathConstructor.class);
-        AssetChecksumGenerator acg = newMock(AssetChecksumGenerator.class);
-
-        expect(pc.constructClientPath("assets", "")).andReturn("/ctx/assets/");
-        expect(acg.generateChecksum(r)).andReturn("abcde");
-
         replay();
 
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(
-                new AssetPathConstructorImpl(request,
-                        baseURLSource, false, "assets", pc, acg), configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
+
+        AssetAlias alias = manager.extractAssetAlias(r);
 
-        String expectedPath = "/ctx/assets/" + expectedClientURL.replace("#", "abcde");
-        assertEquals(manager.toClientURL(r), expectedPath);
+        assertEquals(alias.virtualFolder, expectedFolder);
+        assertEquals(alias.path, expectedPath);
 
         verify();
     }
@@ -110,8 +95,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     @Test
     public void failure_if_path_not_in_mapped_alias_folder()
     {
-        AssetPathConstructor pc = newMock(AssetPathConstructor.class);
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(pc, configuration());
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(configuration());
         Resource resource = mockResource();
 
         expect(resource.getPath()).andReturn("org/example/icons/flag.gif").atLeastOnce();
@@ -120,7 +104,7 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
 
         try
         {
-            manager.toClientURL(resource);
+            manager.extractAssetAlias(resource);
             unreachable();
         } catch (UnknownValueException ex)
         {
@@ -138,10 +122,11 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     {
         return new Object[][]
                 {
-                        {"com/example/mylib/Foo.bar", "mylib/#/Foo.bar"},
-                        {"com/example/mylib/nested/Foo.bar", "mylib/#/nested/Foo.bar"},
-                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal/#/Foo.bar"},
-                        {"org/apache/tapestry5/Foo.bar", "tapestry/#/Foo.bar"},};
+                        {"com/example/mylib/Foo.bar", "mylib", "Foo.bar"},
+                        {"com/example/mylib/nested/Foo.bar", "mylib", "nested/Foo.bar"},
+                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal", "Foo.bar"},
+                        {"org/apache/tapestry5/Foo.bar", "tapestry", "Foo.bar"}
+                };
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
deleted file mode 100644
index bdddceb..0000000
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2006, 2007, 2008, 2009, 2011, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services;
-
-import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.internal.test.InternalBaseTestCase;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
-import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.testng.annotations.Test;
-
-public class ClasspathAssetFactoryTest extends InternalBaseTestCase
-{
-    private final IdentityAssetPathConverter converter = new IdentityAssetPathConverter();
-
-    @Test
-    public void asset_client_URL_is_cached()
-    {
-        Resource r = new ClasspathResource("foo/Bar.txt");
-
-        ClasspathAssetAliasManager aliasManager = mockClasspathAssetAliasManager();
-
-        String expectedClientURL = "/context/asset/foo/Bar.txt";
-
-        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
-
-        replay();
-
-        ClasspathAssetFactory factory = new ClasspathAssetFactory(aliasManager, converter);
-
-        Asset asset = factory.createAsset(r);
-
-        assertEquals(asset.toClientURL(), expectedClientURL);
-
-        // Now, to check the cache:
-
-        assertEquals(asset.toClientURL(), expectedClientURL);
-
-        verify();
-    }
-
-    @Test
-    public void simple_asset_client_URL()
-    {
-        ClasspathAssetAliasManager aliasManager = mockClasspathAssetAliasManager();
-
-        Resource r = new ClasspathResource("foo/Bar.txt");
-
-        String expectedClientURL = "/context/asset/foo/Bar.txt";
-
-        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
-
-        replay();
-
-        AssetFactory factory = new ClasspathAssetFactory(aliasManager, new IdentityAssetPathConverter());
-
-        Asset asset = factory.createAsset(r);
-
-        assertSame(asset.getResource(), r);
-        assertEquals(asset.toClientURL(), expectedClientURL);
-        assertEquals(asset.toString(), expectedClientURL);
-
-        verify();
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
deleted file mode 100644
index cb0fa33..0000000
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2006, 2007, 2009, 2012, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services;
-
-import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.internal.test.InternalBaseTestCase;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.Context;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-
-public class ContextAssetFactoryTest extends InternalBaseTestCase
-{
-    private final IdentityAssetPathConverter converter = new IdentityAssetPathConverter();
-
-    @Test
-    public void root_resource()
-    {
-        Context context = mockContext();
-        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
-
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(apc, context, converter);
-
-        assertEquals(factory.getRootResource().toString(), "context:/");
-
-        verify();
-    }
-
-    @Test
-    public void asset_client_URL() throws IOException
-    {
-        Context context = mockContext();
-        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
-
-        Resource r = new ContextResource(context, "foo/Bar.txt");
-
-        String expectedURL = "/expected-url";
-
-        expect(apc.constructAssetPath("ctx", "foo/Bar.txt", r)).andReturn(expectedURL).atLeastOnce();
-
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(apc, context, new IdentityAssetPathConverter());
-
-        Asset asset = factory.createAsset(r);
-
-        assertSame(asset.getResource(), r);
-
-        assertSame(asset.toClientURL(), expectedURL);
-
-        // In real life, toString() is the same as toClientURL(), but we're testing
-        // that the optimize method is getting called, basically.
-        assertSame(asset.toString(), expectedURL);
-
-        verify();
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
index bdb745c..3836bff 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
@@ -42,7 +42,7 @@ public class ContextAssetRequestHandlerTest extends TestBase
     @Test(dataProvider = "invalid_paths")
     public void ensure_assets_are_rejected(String path) throws IOException
     {
-        ContextAssetRequestHandler handler = new ContextAssetRequestHandler(null, null, null);
+        ContextAssetRequestHandler handler = new ContextAssetRequestHandler(null, null);
 
         assertFalse(handler.handleAssetRequest(null, null, "fake-checksum/" + path),
                 "Handler should return false for invalid path.");
@@ -64,7 +64,7 @@ public class ContextAssetRequestHandlerTest extends TestBase
 
         replay();
 
-        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, gen, root);
+        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, root);
 
         assertFalse(h.handleAssetRequest(req, res, "abc/folder/icon.png"));
 


[09/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/be3e5817
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/be3e5817
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/be3e5817

Branch: refs/heads/master
Commit: be3e581771e1c932c621ce553f9bf2f5102cbb85
Parents: 3c0cb49
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 21:57:22 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../assets/ContextAssetRequestHandlerTest.java     |   29 ---------------
 1 files changed, 0 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/be3e5817/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
index 3836bff..767ed12 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
@@ -14,13 +14,7 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
-import org.apache.tapestry5.internal.services.ResourceStreamer;
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.test.TestBase;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
-import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -47,27 +41,4 @@ public class ContextAssetRequestHandlerTest extends TestBase
         assertFalse(handler.handleAssetRequest(null, null, "fake-checksum/" + path),
                 "Handler should return false for invalid path.");
     }
-
-    @Test
-    public void invalid_checksums_are_treated_as_if_missing() throws IOException
-    {
-        ResourceStreamer streamer = newMock(ResourceStreamer.class);
-        AssetChecksumGenerator gen = newMock(AssetChecksumGenerator.class);
-        Resource root = newMock(Resource.class);
-        Resource r = newMock(Resource.class);
-        Request req = newMock(Request.class);
-        Response res = newMock(Response.class);
-
-        expect(root.forFile("folder/icon.png")).andReturn(r);
-        expect(r.exists()).andReturn(true);
-        expect(gen.generateChecksum(r)).andReturn("def");
-
-        replay();
-
-        AssetRequestHandler h = new ContextAssetRequestHandler(streamer, root);
-
-        assertFalse(h.handleAssetRequest(req, res, "abc/folder/icon.png"));
-
-        verify();
-    }
 }


[05/10] git commit: URLs for compressed assets are now distinct from uncompressed assets

Posted by hl...@apache.org.
URLs for compressed assets are now distinct from uncompressed assets

"/asset/" for normal assets, or "/asset.gz/" for compressed assets
Assets now know about the StreamableResource (necessary to get content type and content checksum)
Content checksum for compressed resources is now based on the compressed bytestream, not the uncompressed resource
Modules are now a special kind of asset
Did a better job of centralizing the logic for creating asset URLs

Tests are currently broken
Peformance may have been affected


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/154dac95
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/154dac95
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/154dac95

Branch: refs/heads/master
Commit: 154dac959c7c6bf264005ad32ba393c459b719dd
Parents: aa465fc
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 12:12:57 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../java/org/apache/tapestry5/SymbolConstants.java |    8 +-
 .../org/apache/tapestry5/TapestryConstants.java    |   19 ++-
 .../internal/services/AbstractAssetFactory.java    |   90 +++++++++++-
 .../internal/services/AssetDispatcher.java         |   20 +++-
 .../services/ClasspathAssetAliasManagerImpl.java   |   42 ++----
 .../internal/services/ClasspathAssetFactory.java   |   95 ++-----------
 .../internal/services/ContextAssetFactory.java     |  109 ++-------------
 .../internal/services/ResourceStreamer.java        |   47 +++---
 .../internal/services/ResourceStreamerImpl.java    |   60 ++++----
 .../services/ResponseCompressionAnalyzerImpl.java  |   14 ++-
 .../services/assets/AssetPathConstructorImpl.java  |   69 +++++-----
 .../assets/BaseStreamableResourceImpl.java         |   84 -----------
 .../internal/services/assets/CSSURLRewriter.java   |    6 +-
 .../internal/services/assets/ChecksumPath.java     |   16 +--
 .../assets/ClasspathAssetRequestHandler.java       |    8 +-
 .../assets/CompressedStreamableResource.java       |   16 +--
 .../assets/ContextAssetRequestHandler.java         |    7 +-
 .../assets/JavaScriptStackAssemblerImpl.java       |    4 +-
 .../services/assets/SRSCompressingInterceptor.java |    7 +-
 .../services/assets/StackAssetRequestHandler.java  |   41 +++---
 .../services/assets/StreamableResourceImpl.java    |   69 ++++++++--
 .../JavaScriptStackPathConstructorImpl.java        |   17 ++-
 .../javascript/MOduleAssetRequestHandler.java      |   94 +++++++++++++
 .../services/javascript/ModuleDispatcher.java      |  107 --------------
 .../services/javascript/ModuleManagerImpl.java     |   38 ++++--
 .../tapestry5/internal/util/VirtualResource.java   |    2 +-
 .../org/apache/tapestry5/services/AssetAlias.java  |   32 +++++
 .../services/ClasspathAssetAliasManager.java       |   12 +-
 .../services/ResponseCompressionAnalyzer.java      |   15 ++-
 .../services/assets/AssetPathConstructor.java      |   40 +++---
 .../services/assets/AssetRequestHandler.java       |    6 +-
 .../tapestry5/services/assets/AssetsModule.java    |   21 ++--
 .../services/assets/StreamableResource.java        |    2 +-
 .../services/javascript/JavaScriptModule.java      |   15 +-
 .../ResponseCompressionAnalyzerTest.groovy         |    2 +-
 .../assets/AssetPathConstructorImplTest.groovy     |    4 +-
 .../ModuleAssetRequestHandlerTest.groovy           |    6 +-
 .../javascript/ModuleDispatcherTests.groovy        |    6 +-
 .../ClasspathAssetAliasManagerImplTest.java        |   47 ++----
 .../services/ClasspathAssetFactoryTest.java        |   78 ----------
 .../internal/services/ContextAssetFactoryTest.java |   74 ----------
 .../assets/ContextAssetRequestHandlerTest.java     |    4 +-
 42 files changed, 620 insertions(+), 833 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 8f16694..ccea4a0 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -154,6 +154,8 @@ public class SymbolConstants
      * necessary to GZIP requests, then override this to "false".
      *
      * @see #MIN_GZIP_SIZE
+     * @see org.apache.tapestry5.services.ResponseCompressionAnalyzer
+     * @see org.apache.tapestry5.services.assets.CompressionAnalyzer
      * @since 5.1.0.0
      */
     public static final String GZIP_COMPRESSION_ENABLED = "tapestry.gzip-compression-enabled";
@@ -330,7 +332,7 @@ public class SymbolConstants
     /**
      * Prefix to be used for all resource paths, used to recognize which requests are for assets. This value
      * is appended to the context path and the (optional {@linkplain #APPLICATION_FOLDER application folder}.
-     * Its default is "assets".  It may contain slashes, but should not begin or end with one. This is the prefix
+     * Its default is "asset".  It may contain slashes, but should not begin or end with one. This is the prefix
      * for uncompressed assets.
      */
     public static final String ASSET_PATH_PREFIX = "tapestry.asset-path-prefix";
@@ -338,7 +340,8 @@ public class SymbolConstants
     /**
      * As with {@link #ASSET_PATH_PREFIX} but for compressed versions of assets. At render time, it is determined
      * whether each asset is compressable (for example, image file formats are already compressed). A path for
-     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix suffixed with ".gz".
+     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix
+     * suffixed with ".gz".
      *
      * @since 5.4
      */
@@ -409,4 +412,5 @@ public class SymbolConstants
      * @since 5.4
      */
     public static final String SESSION_LOCKING_ENABLED = "tapestry.session-locking-enabled";
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
index 38e5994..74adf03 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,12 +15,11 @@
 package org.apache.tapestry5;
 
 import org.apache.tapestry5.internal.structure.PageResetListener;
-import org.apache.tapestry5.runtime.PageLifecycleListener;
 import org.apache.tapestry5.services.ComponentEventLinkEncoder;
 
 /**
  * Constants needed by end-user classes.
- * 
+ *
  * @since 5.2.0
  */
 public class TapestryConstants
@@ -40,12 +39,22 @@ public class TapestryConstants
      * notification} after the initialization event; the
      * LOOPBACK
      * prevents this reset notification.
-     * 
-     * @since 5.2.0
+     *
      * @see ComponentEventLinkEncoder#createPageRenderLink(org.apache.tapestry5.services.PageRenderRequestParameters)
      * @see ComponentEventLinkEncoder#decodePageRenderRequest(org.apache.tapestry5.services.Request)
      * @see PageResetListener
+     * @since 5.2.0
      */
     public static final String PAGE_LOOPBACK_PARAMETER_NAME = "t:lb";
 
+    /**
+     * {@link org.apache.tapestry5.services.Request} attribute set to true or false before an
+     * {@link org.apache.tapestry5.services.assets.AssetRequestHandler} is invoked. This information
+     * is used downstream, typically by {@link org.apache.tapestry5.internal.services.ResourceStreamer},
+     * to decide whether to obtain a normal, or compressed, {@link org.apache.tapestry5.services.assets.StreamableResource}.
+     *
+     * @since 5.4
+     */
+    public static final String COMPRESS_CONTENT = "tapestry.compress-content";
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
index 7caff2c..e230999 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AbstractAssetFactory.java
@@ -14,18 +14,96 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
-import org.apache.tapestry5.internal.util.RecomputableSupport;
-import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.AssetFactory;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+import java.io.IOException;
 
 public abstract class AbstractAssetFactory implements AssetFactory
 {
-    protected final RecomputableSupport recomputable = new RecomputableSupport();
+    private final AssetPathConstructor assetPathConstructor;
+
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
+
+    private final StreamableResourceSource streamableResourceSource;
+
+    private final ResourceChangeTracker resourceChangeTracker;
+
+    private final Resource rootResource;
+
+    public AbstractAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                                ResourceChangeTracker resourceChangeTracker,
+                                StreamableResourceSource streamableResourceSource,
+                                AssetPathConstructor assetPathConstructor,
+                                Resource rootResource)
+    {
+        this.compressionAnalyzer = compressionAnalyzer;
+        this.resourceChangeTracker = resourceChangeTracker;
+        this.streamableResourceSource = streamableResourceSource;
+        this.assetPathConstructor = assetPathConstructor;
+        this.rootResource = rootResource;
+    }
+
+    protected boolean isCompressable(StreamableResource resource)
+    {
+        return compressionAnalyzer.isGZipEnabled(resource.getContentType());
+    }
 
-    @PostInjection
-    public void updateVersionNumberOnResourceChange(ResourceChangeTracker tracker)
+    /**
+     * Returns the root {@link ContextResource}.
+     */
+    public Resource getRootResource()
     {
-        recomputable.initialize(tracker);
+        return rootResource;
+    }
+
+    protected Asset createAsset(final Resource resource, final String folder, final String resourcePath)
+    {
+        assert resource != null;
+        assert InternalUtils.isNonBlank(folder);
+        assert InternalUtils.isNonBlank(resourcePath);
+
+        return new AbstractAsset(false)
+        {
+            @Override
+            public String toClientURL()
+            {
+                // TODO: Some caching to ensure this is fast!  Fortunately, the SRS pipeline includes caching already,
+                // but the results of assetPathConstructor could be cached as well, maybe.
+
+                try
+                {
+                    // Get the uncompressed version, so that we can identify its content type (and remember, the extension is not enough,
+                    // as some types get translated to new content types by the SRS).
+
+                    StreamableResource uncompressed = streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_DISABLED, resourceChangeTracker);
+
+                    StreamableResource forRequest = isCompressable(uncompressed)
+                            ? streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_ENABLED, resourceChangeTracker)
+                            : uncompressed;
+
+                    return assetPathConstructor.constructAssetPath(folder, resourcePath, forRequest);
+                } catch (IOException ex)
+                {
+                    throw new RuntimeException(String.format("Unable to construct asset path for %s: %s",
+                            resource, InternalUtils.toMessage(ex)), ex);
+                }
+            }
+
+            @Override
+            public Resource getResource()
+            {
+                return resource;
+            }
+        };
+
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
index 2ab1a68..526c9d5 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.ioc.annotations.Marker;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
@@ -55,6 +56,20 @@ public class AssetDispatcher implements Dispatcher
 
     private final String uncompressedPathPrefix, compressedPathPrefix;
 
+    private AssetRequestHandler wrap(final boolean compress, final AssetRequestHandler handler)
+    {
+        return new AssetRequestHandler()
+        {
+            @Override
+            public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
+            {
+                request.setAttribute(TapestryConstants.COMPRESS_CONTENT, compress);
+
+                return handler.handleAssetRequest(request, response, extraPath);
+            }
+        };
+    }
+
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
@@ -72,8 +87,8 @@ public class AssetDispatcher implements Dispatcher
         {
             AssetRequestHandler handler = configuration.get(path);
 
-            addPath(uncompressedPathPrefix, path, handler);
-            addPath(compressedPathPrefix, path, handler);
+            addPath(uncompressedPathPrefix, path, wrap(false, handler));
+            addPath(compressedPathPrefix, path, wrap(true, handler));
         }
 
         // Sort by descending length
@@ -124,7 +139,6 @@ public class AssetDispatcher implements Dispatcher
 
         for (String extendedPath : assetPaths)
         {
-
             if (path.startsWith(extendedPath))
             {
                 AssetRequestHandler handler = pathToHandler.get(extendedPath);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
index 7a04a87..9c14d6a 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
@@ -16,13 +16,11 @@ package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -30,8 +28,6 @@ import java.util.Map;
 
 public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManager
 {
-    private final AssetPathConstructor assetPathConstructor;
-
     /**
      * Map from alias to path.
      */
@@ -42,25 +38,18 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
      */
     private final Map<String, String> pathPrefixToAlias = CollectionFactory.newMap();
 
-    private final List<String> sortedAliases;
-
     private final List<String> sortedPathPrefixes;
 
     /**
-     * Configuration is a map of aliases (short names) to complete names. Keys and values should end with a slash, but
-     * one will be provided as necessary, so don't both.
+     * Configuration is a map of aliases (short names) to complete names. Keys and values must not start or end with a slash,
+     * but may contain them.
      */
-    public ClasspathAssetAliasManagerImpl(AssetPathConstructor assetPathConstructor,
-
-                                          Map<String, String> configuration)
+    public ClasspathAssetAliasManagerImpl(Map<String, String> configuration)
     {
-        this.assetPathConstructor = assetPathConstructor;
-
         for (Map.Entry<String, String> e : configuration.entrySet())
         {
             String alias = verify("folder name", e.getKey());
 
-
             String path = verify("path", e.getValue());
 
             aliasToPathPrefix.put(alias, path);
@@ -75,9 +64,6 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
             }
         };
 
-        sortedAliases = CollectionFactory.newList(aliasToPathPrefix.keySet());
-        Collections.sort(sortedAliases, sortDescendingByLength);
-
         sortedPathPrefixes = CollectionFactory.newList(aliasToPathPrefix.values());
         Collections.sort(sortedPathPrefixes, sortDescendingByLength);
     }
@@ -93,7 +79,7 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
 
     }
 
-    public String toClientURL(Resource resource)
+    public AssetAlias extractAssetAlias(Resource resource)
     {
         String resourcePath = resource.getPath();
 
@@ -101,18 +87,18 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
         {
             if (resourcePath.startsWith(pathPrefix))
             {
+                // Prevent matching path prefix "foo" against "foobar" ... it must match against "foo/".
+                if (resourcePath.charAt(pathPrefix.length()) != '/')
+                {
+                    continue;
+                }
+
                 String virtualFolder = pathPrefixToAlias.get(pathPrefix);
 
-                String virtualPath = resourcePath.substring(pathPrefix.length() + 1);
+                // Don't want that slash seperating the folder from the rest of the path.
+                String path = resourcePath.substring(pathPrefix.length() + 1);
 
-                try
-                {
-                    return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath, resource);
-                } catch (IOException ex)
-                {
-                    throw new RuntimeException(String.format("Unable to construct asset path for %s/%s (from %s): %s",
-                            virtualFolder, virtualPath, resource, InternalUtils.toMessage(ex)), ex);
-                }
+                return new AssetAlias(virtualFolder, path);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
index 0173058..1b34fd6 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
@@ -15,13 +15,16 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Marker;
 import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
-import org.apache.tapestry5.services.AssetPathConverter;
+import org.apache.tapestry5.services.AssetAlias;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
 import org.apache.tapestry5.services.ClasspathProvider;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 /**
  * Generates Assets for files on the classpath.
@@ -33,89 +36,23 @@ public class ClasspathAssetFactory extends AbstractAssetFactory
 {
     private final ClasspathAssetAliasManager aliasManager;
 
-    private final ClasspathResource rootResource;
-
-    private final AssetPathConverter converter;
-
-    private final boolean invariant;
-
-    public ClasspathAssetFactory(ClasspathAssetAliasManager aliasManager, AssetPathConverter converter)
+    public ClasspathAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                                 ResourceChangeTracker resourceChangeTracker,
+                                 StreamableResourceSource streamableResourceSource,
+                                 AssetPathConstructor assetPathConstructor,
+                                 ClasspathAssetAliasManager aliasManager)
     {
-        this.aliasManager = aliasManager;
-        this.converter = converter;
-
-        rootResource = new ClasspathResource("");
+        super(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor,
+                new ClasspathResource(""));
 
-        invariant = converter.isInvariant();
-    }
-
-    private String clientURL(Resource resource)
-    {
-        String assetPath = aliasManager.toClientURL(resource);
-
-        return converter.convertAssetPath(assetPath);
+        this.aliasManager = aliasManager;
     }
 
+    @Override
     public Asset createAsset(Resource resource)
     {
-        if (invariant)
-        {
-            return createInvariantAsset(resource);
-        }
-
-        return createVariantAsset(resource);
-    }
-
-    /**
-     * A variant asset must pass the resource through clientURL() all the time; very inefficient.
-     */
-    private Asset createVariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(false)
-        {
-            public Resource getResource()
-            {
-                return resource;
-            }
+        AssetAlias alias = aliasManager.extractAssetAlias(resource);
 
-            public String toClientURL()
-            {
-                return clientURL(resource);
-            }
-        };
-    }
-
-    /**
-     * An invariant asset is normal, and only needs to compute the clientURL for the resource once (
-     * or when the underlying Resource's content has changed).
-     */
-    private Asset createInvariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(true)
-        {
-            private final Invokable<String> clientURL = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return clientURL(resource);
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return clientURL.invoke();
-            }
-        };
-    }
-
-    public Resource getRootResource()
-    {
-        return rootResource;
+        return createAsset(resource, alias.virtualFolder, alias.path);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
index 32d63f7..4731cdf 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
@@ -15,15 +15,13 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.ioc.Invokable;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.AssetPathConverter;
 import org.apache.tapestry5.services.Context;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
-
-import java.io.IOException;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 /**
  * Implementation of {@link AssetFactory} for assets that are part of the web application context.
@@ -32,102 +30,19 @@ import java.io.IOException;
  */
 public class ContextAssetFactory extends AbstractAssetFactory
 {
-    private final AssetPathConstructor assetPathConstructor;
-
-    private final Resource rootResource;
-
-    private final AssetPathConverter converter;
-
-    private final boolean invariant;
-
-    public ContextAssetFactory(AssetPathConstructor assetPathConstructor, Context context,
-
-                               AssetPathConverter converter)
-    {
-        this.assetPathConstructor = assetPathConstructor;
-        this.converter = converter;
-
-        rootResource = new ContextResource(context, "/");
-        invariant = this.converter.isInvariant();
-    }
 
-    public Asset createAsset(Resource resource)
+    public ContextAssetFactory(ResponseCompressionAnalyzer compressionAnalyzer,
+                               ResourceChangeTracker resourceChangeTracker,
+                               StreamableResourceSource streamableResourceSource,
+                               AssetPathConstructor assetPathConstructor,
+                               Context context)
     {
-        if (invariant)
-        {
-            return createInvariantAsset(resource);
-        }
-
-        return createVariantAsset(resource);
-    }
-
-    private String defaultPath(Resource resource)
-    {
-        try
-        {
-            return assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath(), resource);
-        } catch (IOException ex)
-        {
-            throw new RuntimeException(String.format("Unable to construct asset path for %s: %s",
-                    resource, InternalUtils.toMessage(ex)), ex);
-        }
-    }
-
-    private Asset createInvariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(true)
-        {
-            private final Invokable<String> clientURL = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return converter.convertAssetPath(defaultPath(resource));
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return clientURL.invoke();
-            }
-        };
-    }
-
-    private Asset createVariantAsset(final Resource resource)
-    {
-        return new AbstractAsset(false)
-        {
-            private final Invokable<String> defaultPath = recomputable.create(new Invokable<String>()
-            {
-                @Override
-                public String invoke()
-                {
-                    return defaultPath(resource);
-                }
-            });
-
-            public Resource getResource()
-            {
-                return resource;
-            }
-
-            public String toClientURL()
-            {
-                return converter.convertAssetPath(defaultPath.invoke());
-            }
-        };
+        super(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor,
+                new ContextResource(context, "/"));
     }
 
-    /**
-     * Returns the root {@link org.apache.tapestry5.internal.services.ContextResource}.
-     */
-    public Resource getRootResource()
+    public Asset createAsset(final Resource resource)
     {
-        return rootResource;
+        return createAsset(resource, RequestConstants.CONTEXT_FOLDER, resource.getPath());
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
index cf4eefd..81027bf 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2011 The Apache Software Foundation
+// Copyright 2006, 2008, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20,18 +20,22 @@ import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.EnumSet;
 import java.util.Set;
 
 /**
- * Responsible for streaming the contents of a resource to the client. The {@link org.apache.tapestry5.ioc.Resource} to
- * stream is a {@link org.apache.tapestry5.ioc.internal.util.ClasspathResource} or {@link ContextResource}.
+ * Responsible for streaming the contents of a resource to the client. This is sometimes a simple
+ * {@link Resource} (often from the {@link org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler},
+ * or more frequently an asset represented as a {@link StreamableResource} (via {@link AssetDispatcher}, {@link org.apache.tapestry5.services.assets.AssetRequestHandler},
+ * and {@link StreamableResourceSource}). As of 5.4, the ResourceStreamer handles ETag support, as well as
+ * validation of the checksum (provided in the URL).
  *
  * @since 5.1.0.0
  */
 public interface ResourceStreamer
 {
     /**
-     * Used to customize the behavior of {@link #streamResource(org.apache.tapestry5.ioc.Resource, java.util.Set)}.
+     * Used to customize the behavior of {@link #streamResource(org.apache.tapestry5.ioc.Resource, java.lang.String, java.util.Set)}.
      *
      * @since 5.4
      */
@@ -44,42 +48,37 @@ public interface ResourceStreamer
         OMIT_EXPIRATION
     }
 
-    /**
-     * Streams the content of the resource to the client (or sends
-     * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}). Encapsulates logic for compression
-     * and for caching.
-     *
-     * @see StreamableResourceSource
-     */
-    void streamResource(Resource resource) throws IOException;
+    static final Set<Options> DEFAULT_OPTIONS = EnumSet.noneOf(Options.class);
 
     /**
      * Streams the content of the resource to the client (or sends
      * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}). Encapsulates logic for compression
      * and for caching.
      *
-     * @see StreamableResourceSource
-     */
-    void streamResource(Resource resource, Set<Options> options) throws IOException;
-
-    /**
-     * Streams a resource that has been assembled elsewhere, using default options of none.
-     *
      * @param resource
-     * @throws IOException
-     * @since 5.3
+     *         to stream
+     * @param providedChecksum
+     *         checksum from URL (or null to not validate against checksum, which is normal for modules)
+     * @param options
+     *         enable or disable certain features
+     * @see StreamableResourceSource
      */
-    void streamResource(StreamableResource resource) throws IOException;
+    boolean streamResource(Resource resource, String providedChecksum, Set<Options> options) throws IOException;
 
     /**
      * Streams a resource that has been assembled elsewhere.
      *
      * @param resource
      *         content to stream
+     * @param providedChecksum
+     *         checksum provided (in the URL) to validate against the {@linkplain org.apache.tapestry5.services.assets.StreamableResource#getChecksum()} actual checksum}
+     *         for the resource, may be blank to not validate against the checksum
      * @param options
-     *         any special options related to streaming the resource
+     *         enable or disable certain features
+     * @return true if the request was handled (even if sending a {@link HttpServletResponse#SC_NOT_MODIFIED} response),
+     *         or false if the request was not handled (because the provided checksum did not match the actual checksum).
      * @throws IOException
      * @since 5.4
      */
-    void streamResource(StreamableResource resource, Set<Options> options) throws IOException;
+    boolean streamResource(StreamableResource resource, String providedChecksum, Set<Options> options) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
index 6020adc..6b41cc8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.IOOperation;
@@ -23,7 +24,6 @@ import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
@@ -32,37 +32,32 @@ import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.EnumSet;
 import java.util.Set;
 
 public class ResourceStreamerImpl implements ResourceStreamer
 {
     static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
 
+    private static final String QUOTE = "\"";
+
     private final Request request;
 
     private final Response response;
 
     private final StreamableResourceSource streamableResourceSource;
 
-    private final ResponseCompressionAnalyzer analyzer;
-
     private final boolean productionMode;
 
     private final OperationTracker tracker;
 
     private final ResourceChangeTracker resourceChangeTracker;
 
-    private final Set<Options> defaultOptions = EnumSet.noneOf(Options.class);
-
     public ResourceStreamerImpl(Request request,
 
                                 Response response,
 
                                 StreamableResourceSource streamableResourceSource,
 
-                                ResponseCompressionAnalyzer analyzer,
-
                                 OperationTracker tracker,
 
                                 @Symbol(SymbolConstants.PRODUCTION_MODE)
@@ -74,52 +69,53 @@ public class ResourceStreamerImpl implements ResourceStreamer
         this.response = response;
         this.streamableResourceSource = streamableResourceSource;
 
-        this.analyzer = analyzer;
         this.tracker = tracker;
         this.productionMode = productionMode;
         this.resourceChangeTracker = resourceChangeTracker;
     }
 
-    public void streamResource(Resource resource) throws IOException
-    {
-        streamResource(resource, defaultOptions);
-    }
-
-    public void streamResource(final Resource resource, final Set<Options> options) throws IOException
+    public boolean streamResource(final Resource resource, final String providedChecksum, final Set<Options> options) throws IOException
     {
         if (!resource.exists())
         {
+            // TODO: Or should we just return false here and not send back a specific error with the (eventual) 404?
+
             response.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("Unable to locate asset '%s' (the file does not exist).", resource));
-            return;
+
+            return true;
         }
 
-        tracker.perform(String.format("Streaming %s", resource), new IOOperation<Void>()
+        final boolean compress = (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
+
+
+        return tracker.perform(String.format("Streaming %s%s", resource, compress ? " (compressed)" : ""), new IOOperation<Boolean>()
         {
-            public Void perform() throws IOException
+            public Boolean perform() throws IOException
             {
-                StreamableResourceProcessing processing = analyzer.isGZipSupported()
+                StreamableResourceProcessing processing = compress
                         ? StreamableResourceProcessing.COMPRESSION_ENABLED
                         : StreamableResourceProcessing.COMPRESSION_DISABLED;
 
                 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
 
-                streamResource(streamable, options);
-
-                return null;
+                return streamResource(streamable, providedChecksum, options);
             }
         });
     }
 
-    public void streamResource(StreamableResource resource) throws IOException
-    {
-        streamResource(resource, defaultOptions);
-    }
-
-    public void streamResource(StreamableResource streamable, Set<Options> options) throws IOException
+    public boolean streamResource(StreamableResource streamable, String providedChecksum, Set<Options> options) throws IOException
     {
         assert streamable != null;
+        assert providedChecksum != null;
         assert options != null;
 
+        String actualChecksum = streamable.getChecksum();
+
+        if (providedChecksum.length() > 0 && !providedChecksum.equals(actualChecksum))
+        {
+            return false;
+        }
+
         long lastModified = streamable.getLastModified();
 
         long ifModifiedSince = 0;
@@ -137,11 +133,11 @@ public class ResourceStreamerImpl implements ResourceStreamer
         if (ifModifiedSince > 0 && ifModifiedSince >= lastModified)
         {
             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-            return;
+            return true;
         }
 
         // ETag should be surrounded with quotes.
-        String token = "\"" + streamable.getChecksum() + "\"";
+        String token = QUOTE + actualChecksum + QUOTE;
 
         // Even when sending a 304, we want the ETag associated with the request.
         // In most cases (except JavaScript modules), the checksum is also embedded into the URL.
@@ -155,7 +151,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
         if (token.equals(providedToken))
         {
             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-            return;
+            return true;
         }
 
         // Prevent the upstream code from compressing when we don't want to.
@@ -184,6 +180,8 @@ public class ResourceStreamerImpl implements ResourceStreamer
         streamable.streamTo(os);
 
         os.close();
+
+        return true;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
index 7432019..7bea13c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010, 2012 The Apache Software Foundation
+// Copyright 2009-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -27,12 +28,15 @@ public class ResponseCompressionAnalyzerImpl implements ResponseCompressionAnaly
 
     private final boolean gzipCompressionEnabled;
 
+    private final CompressionAnalyzer compressionAnalyzer;
+
     public ResponseCompressionAnalyzerImpl(HttpServletRequest request,
                                            @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
-                                           boolean gzipCompressionEnabled)
+                                           boolean gzipCompressionEnabled, CompressionAnalyzer compressionAnalyzer)
     {
         this.request = request;
         this.gzipCompressionEnabled = gzipCompressionEnabled;
+        this.compressionAnalyzer = compressionAnalyzer;
     }
 
     public boolean isGZipSupported()
@@ -65,4 +69,10 @@ public class ResponseCompressionAnalyzerImpl implements ResponseCompressionAnaly
 
         return false;
     }
+
+    @Override
+    public boolean isGZipEnabled(String contentType)
+    {
+        return isGZipSupported() && compressionAnalyzer.isCompressable(contentType);
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
index 6ba0d72..09e434f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
@@ -15,15 +15,14 @@
 package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.internal.services.RequestConstants;
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.AssetPathConverter;
 import org.apache.tapestry5.services.BaseURLSource;
 import org.apache.tapestry5.services.PathConstructor;
 import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 
 import java.io.IOException;
@@ -32,11 +31,11 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 {
     private final Request request;
 
-    private final String prefix;
+    private final String uncompressedPrefix, compressedPrefix;
 
     private final BaseURLSource baseURLSource;
 
-    private final AssetChecksumGenerator assetChecksumGenerator;
+    private final AssetPathConverter pathConverter;
 
     private final boolean fullyQualified;
 
@@ -47,44 +46,53 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
                                     boolean fullyQualified,
 
                                     @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                                    String assetPathPrefix,
+                                    String uncompressedAssetPrefix,
+
+                                    @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
+                                    String compressedAssetPrefix,
 
                                     PathConstructor pathConstructor,
 
-                                    AssetChecksumGenerator assetChecksumGenerator)
+                                    AssetPathConverter pathConverter)
     {
         this.request = request;
         this.baseURLSource = baseURLSource;
 
         this.fullyQualified = fullyQualified;
-        this.assetChecksumGenerator = assetChecksumGenerator;
+        this.pathConverter = pathConverter;
 
-        prefix = pathConstructor.constructClientPath(assetPathPrefix, "");
+        uncompressedPrefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
+        compressedPrefix = pathConstructor.constructClientPath(compressedAssetPrefix, "");
     }
 
-    public String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException
+    public String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException
     {
-        StringBuilder builder = new StringBuilder();
+        assert InternalUtils.isNonBlank(path);
 
-        if (fullyQualified)
-        {
-            builder.append(baseURLSource.getBaseURL(request.isSecure()));
-        }
+        StringBuilder builder = create(resource.getCompression() == CompressionStatus.COMPRESSED, virtualFolder);
+        builder.append("/");
 
-        builder.append(prefix);  // ends with a slash
+        builder.append(resource.getChecksum());
 
-        builder.append(RequestConstants.STACK_FOLDER).append("/");
-        builder.append(assetChecksumGenerator.generateChecksum(resource)).append("/");
-        builder.append(localeName).append("/");
+        builder.append('/');
         builder.append(path);
 
-        return builder.toString();
+        return finish(builder);
+    }
+
+    public String constructAssetPath(String virtualFolder, boolean compressed)
+    {
+        return finish(create(compressed, virtualFolder));
     }
 
-    public String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException
+    private String finish(StringBuilder builder)
+    {
+        return pathConverter.convertAssetPath(builder.toString());
+    }
+
+    private StringBuilder create(boolean compress, String virtualFolder)
     {
         assert InternalUtils.isNonBlank(virtualFolder);
-        assert path != null;
 
         StringBuilder builder = new StringBuilder();
 
@@ -93,21 +101,8 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
             builder.append(baseURLSource.getBaseURL(request.isSecure()));
         }
 
-        builder.append(prefix);
-        builder.append(virtualFolder);
-        builder.append("/");
-
-        builder.append(assetChecksumGenerator.generateChecksum(resource));
-
-        // TODO: Under what conditions would the path ever be blank? Is this allowed? It may have made sense
-        // in 5.3, to allow access to a folder (to allow the client to build relative URLs), but that
-        // is no longer true in 5.4 because of the checksum in the URL.
-        if (InternalUtils.isNonBlank(path))
-        {
-            builder.append('/');
-            builder.append(path);
-        }
+        builder.append(compress ? compressedPrefix : uncompressedPrefix);
 
-        return builder.toString();
+        return builder.append(virtualFolder);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
deleted file mode 100644
index c30eb33..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/BaseStreamableResourceImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services.assets;
-
-import org.apache.tapestry5.services.assets.CompressionStatus;
-import org.apache.tapestry5.services.assets.StreamableResource;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public abstract class BaseStreamableResourceImpl implements StreamableResource
-{
-    protected final String description, contentType;
-
-    protected final CompressionStatus compression;
-
-    protected final long lastModified;
-
-    protected final BytestreamCache bytestreamCache;
-
-    public BaseStreamableResourceImpl(long lastModified, String description, BytestreamCache bytestreamCache, String contentType, CompressionStatus compression)
-    {
-        this.lastModified = lastModified;
-        this.description = description;
-        this.bytestreamCache = bytestreamCache;
-        this.contentType = contentType;
-        this.compression = compression;
-    }
-
-    public String getDescription()
-    {
-        return description;
-    }
-
-    public CompressionStatus getCompression()
-    {
-        return compression;
-    }
-
-    public String getContentType()
-    {
-        return contentType;
-    }
-
-    public int getSize()
-    {
-        return bytestreamCache.size();
-    }
-
-    public long getLastModified()
-    {
-        return lastModified;
-    }
-
-    public void streamTo(OutputStream os) throws IOException
-    {
-        bytestreamCache.writeTo(os);
-    }
-
-    public InputStream openStream() throws IOException
-    {
-        return bytestreamCache.openStream();
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("StreamableResource<%s %s %s lastModified: %tc size: %d>", contentType, description, compression.name(),
-                lastModified, getSize());
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
index 2dec879..fc673fa 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CSSURLRewriter.java
@@ -34,11 +34,15 @@ import java.util.regex.Pattern;
  * the CSS file and the image assets it may refer to (useful for incorporating a hash of the resource's content into
  * the exposed URL).
  *
+ * <p>
+ * One potential problem with URL rewriting is the way that URLs for referenced resources are generated; we are
+ * somewhat banking on the fact that referenced resources are non-compressable images.
+ *
  * @since 5.4
  */
 public class CSSURLRewriter extends DelegatingSRS
 {
-    // Group 1 is the optional single or double quote
+    // Group 1 is the optional single or double quote (note the use of backtracking to match it)
     // Group 2 is the text inside the quotes, or inside the parens if no quotes
     private final Pattern urlPattern = Pattern.compile("url\\(\\s*(['\"]?)(.+?)\\1\\s*\\)", Pattern.MULTILINE);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
index da6dc77..074fd86 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
@@ -16,7 +16,6 @@ package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 
 import java.io.IOException;
 
@@ -34,12 +33,9 @@ public class ChecksumPath
 
     private final ResourceStreamer streamer;
 
-    private final AssetChecksumGenerator assetChecksumGenerator;
-
-    public ChecksumPath(ResourceStreamer streamer, AssetChecksumGenerator assetChecksumGenerator, String baseFolder, String extraPath)
+    public ChecksumPath(ResourceStreamer streamer, String baseFolder, String extraPath)
     {
         this.streamer = streamer;
-        this.assetChecksumGenerator = assetChecksumGenerator;
         int slashx = extraPath.indexOf('/');
 
         checksum = extraPath.substring(0, slashx);
@@ -67,15 +63,7 @@ public class ChecksumPath
             return false;
         }
 
-        String actualChecksum = assetChecksumGenerator.generateChecksum(resource);
-
-        if (!actualChecksum.equals(checksum))
-        {
-            return false;
-        }
-
-        streamer.streamResource(resource);
 
-        return true;
+        return streamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
index 0a7e39a..b2bdeff 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
@@ -20,7 +20,6 @@ import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
 import java.io.IOException;
@@ -35,24 +34,21 @@ public class ClasspathAssetRequestHandler implements AssetRequestHandler
 {
     private final ResourceStreamer streamer;
 
-    private final AssetChecksumGenerator checksumGenerator;
-
     private final AssetSource assetSource;
 
     private final String baseFolder;
 
     public ClasspathAssetRequestHandler(ResourceStreamer streamer,
-                                        AssetChecksumGenerator checksumGenerator, AssetSource assetSource, String baseFolder)
+                                        AssetSource assetSource, String baseFolder)
     {
         this.streamer = streamer;
-        this.checksumGenerator = checksumGenerator;
         this.assetSource = assetSource;
         this.baseFolder = baseFolder;
     }
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        ChecksumPath path = new ChecksumPath(streamer, checksumGenerator, baseFolder, extraPath);
+        ChecksumPath path = new ChecksumPath(streamer, baseFolder, extraPath);
 
         Resource resource = assetSource.resourceForPath(path.resourcePath);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
index 5f447b4..5dc7ea2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressedStreamableResource.java
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.CompressionStatus;
 import org.apache.tapestry5.services.assets.StreamableResource;
 
@@ -28,22 +29,13 @@ import java.util.zip.GZIPOutputStream;
  *
  * @since 5.4
  */
-public class CompressedStreamableResource extends BaseStreamableResourceImpl
+public class CompressedStreamableResource extends StreamableResourceImpl
 {
-    private final StreamableResource base;
-
-    public String getChecksum() throws IOException
+    public CompressedStreamableResource(StreamableResource base, AssetChecksumGenerator assetChecksumGenerator) throws IOException
     {
-        return base.getChecksum();
-    }
-
-    public CompressedStreamableResource(StreamableResource base) throws IOException
-    {
-        super(base.getLastModified(), base.getDescription(), compressContent(base), base.getContentType(), CompressionStatus.COMPRESSED);
+        super(base.getDescription(), base.getContentType(), CompressionStatus.COMPRESSED, base.getLastModified(), compressContent(base), assetChecksumGenerator);
 
         assert base.getCompression() == CompressionStatus.COMPRESSABLE;
-
-        this.base = base;
     }
 
     private static BytestreamCache compressContent(StreamableResource resource) throws IOException

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
index 72a634c..75a4b6c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
@@ -18,7 +18,6 @@ import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
 import java.io.IOException;
@@ -34,21 +33,19 @@ public class ContextAssetRequestHandler implements AssetRequestHandler
 {
     private final ResourceStreamer resourceStreamer;
 
-    private final AssetChecksumGenerator checksumGenerator;
     private final Resource rootContextResource;
 
     private final Pattern illegal = Pattern.compile("^(((web|meta)-inf.*)|(.*\\.tml$))", Pattern.CASE_INSENSITIVE);
 
-    public ContextAssetRequestHandler(ResourceStreamer resourceStreamer, AssetChecksumGenerator checksumGenerator, Resource rootContextResource)
+    public ContextAssetRequestHandler(ResourceStreamer resourceStreamer, Resource rootContextResource)
     {
         this.resourceStreamer = resourceStreamer;
-        this.checksumGenerator = checksumGenerator;
         this.rootContextResource = rootContextResource;
     }
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        ChecksumPath path = new ChecksumPath(resourceStreamer, checksumGenerator, null, extraPath);
+        ChecksumPath path = new ChecksumPath(resourceStreamer, null, extraPath);
 
         if (illegal.matcher(path.resourcePath).matches())
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
index 92d849a..ece7533 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
@@ -94,7 +94,7 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
         {
             StreamableResource uncompressed = assembleJavascriptResourceForStack(locale, stackName, false);
 
-            return new CompressedStreamableResource(uncompressed);
+            return new CompressedStreamableResource(uncompressed, checksumGenerator);
         }
 
         JavaScriptStack stack = stackSource.getStack(stackName);
@@ -117,7 +117,7 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
         for (Asset library : libraries)
         {
-            String path = library.toClientURL();
+            String path = library.getResource().toString();
 
             paths.put(path);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
index 3781931..f772930 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
@@ -23,10 +23,13 @@ public class SRSCompressingInterceptor extends DelegatingSRS
 {
     private final int compressionCutoff;
 
-    public SRSCompressingInterceptor(StreamableResourceSource delegate, int compressionCutoff)
+    private final AssetChecksumGenerator checksumGenerator;
+
+    public SRSCompressingInterceptor(StreamableResourceSource delegate, int compressionCutoff, AssetChecksumGenerator checksumGenerator)
     {
         super(delegate);
         this.compressionCutoff = compressionCutoff;
+        this.checksumGenerator = checksumGenerator;
     }
 
     public StreamableResource getStreamableResource(Resource baseResource, StreamableResourceProcessing processing, ResourceDependencies dependencies)
@@ -54,6 +57,6 @@ public class SRSCompressingInterceptor extends DelegatingSRS
             return uncompressed;
         }
 
-        return new CompressedStreamableResource(uncompressed);
+        return new CompressedStreamableResource(uncompressed, checksumGenerator);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
index 7edff7f..614cf73 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
@@ -14,13 +14,13 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.ioc.IOOperation;
 import org.apache.tapestry5.ioc.OperationTracker;
 import org.apache.tapestry5.services.LocalizationSetter;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.slf4j.Logger;
@@ -35,8 +35,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
 
     private final LocalizationSetter localizationSetter;
 
-    private final ResponseCompressionAnalyzer compressionAnalyzer;
-
     private final ResourceStreamer resourceStreamer;
 
     // Group 1: checksum
@@ -48,18 +46,19 @@ public class StackAssetRequestHandler implements AssetRequestHandler
 
     private final JavaScriptStackAssembler javaScriptStackAssembler;
 
+    private final Request request;
+
     public StackAssetRequestHandler(Logger logger, LocalizationSetter localizationSetter,
-                                    ResponseCompressionAnalyzer compressionAnalyzer,
                                     ResourceStreamer resourceStreamer,
                                     OperationTracker tracker,
-                                    JavaScriptStackAssembler javaScriptStackAssembler)
+                                    JavaScriptStackAssembler javaScriptStackAssembler, Request request)
     {
         this.logger = logger;
         this.localizationSetter = localizationSetter;
-        this.compressionAnalyzer = compressionAnalyzer;
         this.resourceStreamer = resourceStreamer;
         this.tracker = tracker;
         this.javaScriptStackAssembler = javaScriptStackAssembler;
+        this.request = request;
     }
 
     public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
@@ -69,23 +68,12 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                 {
                     public Boolean perform() throws IOException
                     {
-                        boolean compress = compressionAnalyzer.isGZipSupported();
-
-                        StreamableResource resource = getResource(extraPath, compress);
-
-                        if (resource == null)
-                        {
-                            return false;
-                        }
-
-                        resourceStreamer.streamResource(resource);
-
-                        return true;
+                        return streamStackResource(extraPath);
                     }
                 });
     }
 
-    private StreamableResource getResource(String extraPath, final boolean compressed) throws IOException
+    private boolean streamStackResource(String extraPath) throws IOException
     {
         Matcher matcher = pathPattern.matcher(extraPath);
 
@@ -93,7 +81,7 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         {
             logger.warn(String.format("Unable to parse '%s' as an asset stack path", extraPath));
 
-            return null;
+            return false;
         }
 
         String checksum = matcher.group(1);
@@ -112,13 +100,20 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                         {
                             public StreamableResource perform() throws IOException
                             {
+                                boolean compressed =
+                                        (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
+
                                 return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
 
                             }
                         });
 
-        return checksum.equals(resource.getChecksum())
-                ? resource
-                : null;
+
+        if (resource == null)
+        {
+            return false;
+        }
+
+        return resourceStreamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
index fbf8156..6e4e7e7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -22,23 +22,74 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-public class StreamableResourceImpl extends BaseStreamableResourceImpl
+public class StreamableResourceImpl implements StreamableResource
 {
-    private final AssetChecksumGenerator assetChecksumGenerator;
+    protected final String description, contentType;
 
-    public StreamableResourceImpl(String description, String contentType, CompressionStatus compression, long lastModified,
-                                  BytestreamCache bytestreamCache, AssetChecksumGenerator assetChecksumGenerator)
-    {
-        super(lastModified, description, bytestreamCache, contentType, compression);
+    protected final CompressionStatus compression;
+
+    protected final long lastModified;
+
+    protected final BytestreamCache bytestreamCache;
+
+    protected final AssetChecksumGenerator assetChecksumGenerator;
 
+    public StreamableResourceImpl(String description, String contentType, CompressionStatus compression, long lastModified, BytestreamCache bytestreamCache, AssetChecksumGenerator assetChecksumGenerator)
+    {
+        this.lastModified = lastModified;
+        this.description = description;
+        this.bytestreamCache = bytestreamCache;
+        this.contentType = contentType;
+        this.compression = compression;
         this.assetChecksumGenerator = assetChecksumGenerator;
     }
 
+    public String getDescription()
+    {
+        return description;
+    }
+
+    public CompressionStatus getCompression()
+    {
+        return compression;
+    }
+
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+    public int getSize()
+    {
+        return bytestreamCache.size();
+    }
+
+    public long getLastModified()
+    {
+        return lastModified;
+    }
+
+    public void streamTo(OutputStream os) throws IOException
+    {
+        bytestreamCache.writeTo(os);
+    }
+
+    public InputStream openStream() throws IOException
+    {
+        return bytestreamCache.openStream();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("StreamableResource<%s %s %s lastModified: %tc size: %d>", contentType, description, compression.name(),
+                lastModified, getSize());
+    }
+
     public String getChecksum() throws IOException
     {
         // Currently, we rely on AssetChecksumGenerator to manage a cache, but that may be better done
-        // here (but must be threadsafe).
+        // here (but must be threads-afe).
         return assetChecksumGenerator.generateChecksum(this);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index 3ff8c06..1f2ab02 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -18,11 +18,13 @@ import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.internal.services.RequestConstants;
 import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.services.ThreadLocale;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
@@ -41,6 +43,8 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
 
     private final JavaScriptStackAssembler assembler;
 
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
+
     private final boolean combineScripts;
 
     private final Mapper<Asset, String> toPath = new Mapper<Asset, String>()
@@ -54,13 +58,14 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
                                               JavaScriptStackSource javascriptStackSource,
                                               JavaScriptStackAssembler assembler,
-                                              @Symbol(SymbolConstants.COMBINE_SCRIPTS)
-                                              boolean combineScripts)
+                                              ResponseCompressionAnalyzer compressionAnalyzer, @Symbol(SymbolConstants.COMBINE_SCRIPTS)
+    boolean combineScripts)
     {
         this.threadLocale = threadLocale;
         this.assetPathConstructor = assetPathConstructor;
         this.javascriptStackSource = javascriptStackSource;
         this.assembler = assembler;
+        this.compressionAnalyzer = compressionAnalyzer;
         this.combineScripts = combineScripts;
     }
 
@@ -89,11 +94,13 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     {
         try
         {
-            StreamableResource streamable = assembler.assembleJavaScriptResourceForStack(stackName, false);
+            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported());
 
-            String path = stackName + ".js";
+            String path = String.format("%s/%s.js",
+                    threadLocale.getLocale(),
+                    stackName);
 
-            String stackURL = assetPathConstructor.constructStackAssetPath(threadLocale.getLocale().toString(), path, streamable);
+            String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, assembled);
 
             return CollectionFactory.newList(stackURL);
         } catch (IOException ex)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
new file mode 100644
index 0000000..d809556
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/MOduleAssetRequestHandler.java
@@ -0,0 +1,94 @@
+// Copyright 2012, 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.javascript;
+
+import org.apache.tapestry5.internal.services.AssetDispatcher;
+import org.apache.tapestry5.internal.services.ResourceStreamer;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
+import org.apache.tapestry5.services.javascript.ModuleManager;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name,
+ * and searches for the corresponding JavaScript module.  Unlike normal assets, modules do not include any kind of checksum
+ * in the URL, and do not set a far-future expires header.
+ *
+ * @see ModuleManager
+ */
+public class ModuleAssetRequestHandler implements AssetRequestHandler
+{
+    private final ModuleManager moduleManager;
+
+    private final ResourceStreamer streamer;
+
+    private final OperationTracker tracker;
+
+    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
+
+    public ModuleAssetRequestHandler(ModuleManager moduleManager,
+                                     ResourceStreamer streamer,
+                                     OperationTracker tracker)
+    {
+        this.moduleManager = moduleManager;
+        this.streamer = streamer;
+        this.tracker = tracker;
+    }
+
+    @Override
+    public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
+    {
+        // Ensure request ends with '.js'.  That's the extension tacked on by RequireJS because it expects there
+        // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to
+        // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath
+        // as a module.
+
+        int dotx = extraPath.lastIndexOf('.');
+
+        if (dotx < 0)
+        {
+            return false;
+        }
+
+        if (!extraPath.substring(dotx + 1).equals("js"))
+        {
+            return false;
+        }
+
+        final String moduleName = extraPath.substring(0, dotx);
+
+        return tracker.perform(String.format("Streaming module %s", extraPath), new IOOperation<Boolean>()
+        {
+            public Boolean perform() throws IOException
+            {
+                Resource resource = moduleManager.findResourceForModule(moduleName);
+
+                if (resource != null)
+                {
+                    return streamer.streamResource(resource, "", omitExpiration);
+                }
+
+                return false;
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/154dac95/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
deleted file mode 100644
index 85e2279..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2012, 2013 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry5.internal.services.javascript;
-
-import org.apache.tapestry5.internal.services.AssetDispatcher;
-import org.apache.tapestry5.internal.services.ResourceStreamer;
-import org.apache.tapestry5.ioc.IOOperation;
-import org.apache.tapestry5.ioc.OperationTracker;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.services.Dispatcher;
-import org.apache.tapestry5.services.PathConstructor;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
-import org.apache.tapestry5.services.javascript.ModuleManager;
-
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name,
- * and searches for the corresponding JavaScript module.
- */
-public class ModuleDispatcher implements Dispatcher
-{
-    private final ModuleManager moduleManager;
-
-    private final ResourceStreamer streamer;
-
-    private final OperationTracker tracker;
-
-    private final String prefix;
-
-    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
-
-    public ModuleDispatcher(ModuleManager moduleManager,
-                            ResourceStreamer streamer,
-                            PathConstructor pathConstructor,
-                            OperationTracker tracker)
-    {
-        this.moduleManager = moduleManager;
-        this.streamer = streamer;
-        this.tracker = tracker;
-
-        prefix = pathConstructor.constructDispatchPath("modules", "");
-    }
-
-    public boolean dispatch(Request request, Response response) throws IOException
-    {
-        String requestPath = request.getPath();
-
-        if (!requestPath.startsWith(prefix))
-        {
-            return false;
-        }
-
-        String extraPath = requestPath.substring(prefix.length());
-
-        // Ensure request ends with '.js'.  That's the extension tacked on by RequireJS because it expects there
-        // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to
-        // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath
-        // as a module.
-
-        int dotx = extraPath.lastIndexOf('.');
-
-        if (dotx < 0)
-        {
-            return false;
-        }
-
-        if (!extraPath.substring(dotx + 1).equals("js"))
-        {
-            return false;
-        }
-
-        final String moduleName = extraPath.substring(0, dotx);
-
-        return tracker.perform(String.format("Streaming module %s", extraPath), new IOOperation<Boolean>()
-        {
-            public Boolean perform() throws IOException
-            {
-                Resource resource = moduleManager.findResourceForModule(moduleName);
-
-                if (resource != null)
-                {
-                    streamer.streamResource(resource, omitExpiration);
-
-                    return true;
-                }
-
-                return false;
-            }
-        });
-    }
-}


[06/10] git commit: Modify AssetDispatcher to handle two path prefixes: normal and compressed

Posted by hl...@apache.org.
Modify AssetDispatcher to handle two path prefixes: normal and compressed


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/aa465fcd
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/aa465fcd
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/aa465fcd

Branch: refs/heads/master
Commit: aa465fcd3b4b735da9682f377e43553110303494
Parents: 6fd011e
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sat Apr 13 08:25:00 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../src/main/java/org/apache/tapestry5/Asset.java  |    5 ++
 .../java/org/apache/tapestry5/SymbolConstants.java |   12 +++-
 .../internal/services/AssetDispatcher.java         |   50 +++++++++++----
 .../tapestry5/services/assets/AssetsModule.java    |    1 +
 4 files changed, 55 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java b/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
index b2c76ac..a7a9f25 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/Asset.java
@@ -40,8 +40,13 @@ public interface Asset
      * <p/>Note that starting in Tapestry 5.4, it is expected that Asset instances recognize
      * when the underlying Resource's content has changed, and update the clientURL to reflect the new content's
      * checksum. This wasn't an issue in earlier releases where the clientURL incorporated a version number.
+     * <p/>
+     * Finally, starting in 5.4, this value will often be <em>variant</em>: the exact URL returned will depend on
+     * whether the underlying resource content is compressable, whether the current {@link org.apache.tapestry5.services.Request}
+     * supports compression.
      *
      * @see org.apache.tapestry5.services.AssetSource
+     * @see org.apache.tapestry5.services.AssetPathConverter
      */
     String toClientURL();
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 0d671c4..8f16694 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -330,11 +330,21 @@ public class SymbolConstants
     /**
      * Prefix to be used for all resource paths, used to recognize which requests are for assets. This value
      * is appended to the context path and the (optional {@linkplain #APPLICATION_FOLDER application folder}.
-     * Its default is "assets".  It may contain slashes, but should not begin or end with one.
+     * Its default is "assets".  It may contain slashes, but should not begin or end with one. This is the prefix
+     * for uncompressed assets.
      */
     public static final String ASSET_PATH_PREFIX = "tapestry.asset-path-prefix";
 
     /**
+     * As with {@link #ASSET_PATH_PREFIX} but for compressed versions of assets. At render time, it is determined
+     * whether each asset is compressable (for example, image file formats are already compressed). A path for
+     * either {@link #ASSET_PATH_PREFIX} or this prefix is selected at render time.  Defaults to the asset path prefix suffixed with ".gz".
+     *
+     * @since 5.4
+     */
+    public static final String COMPRESSED_ASSET_PATH_PREFIX = "tapestry.compressed-asset-path-prefix";
+
+    /**
      * The Asset path to the copy of Require JS to include in rendered pages. Any page that makes use of any JavaScript will
      * automatically import this library (as well as the core JavaScript stack).
      *

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
index 4f8e8a7..2ab1a68 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetDispatcher.java
@@ -30,8 +30,10 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * Recognizes requests where the path begins with "/asset/" and delivers the content therein as a bytestream. Also
- * handles requests that are simply polling for a change to the file.
+ * Recognizes requests where the path begins with "/asset/" (actually, as defined by the
+ * {@link SymbolConstants#ASSET_PATH_PREFIX} symbol), and delivers the content therein as a bytestream. Also
+ * handles requests that are simply polling for a change to the file (including checking the ETag in the
+ * request against {@linkplain org.apache.tapestry5.services.assets.StreamableResource#getChecksum() the asset's checksum}.
  *
  * @see ResourceStreamer
  * @see ClasspathAssetAliasManager
@@ -51,26 +53,27 @@ public class AssetDispatcher implements Dispatcher
      */
     private final List<String> assetPaths = CollectionFactory.newList();
 
-    private final String pathPrefix;
+    private final String uncompressedPathPrefix, compressedPathPrefix;
 
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
 
                            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                           String assetPathPrefix)
+                           String uncompressedPrefix,
+
+                           @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
+                           String compressedPrefix)
     {
-        pathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
+        uncompressedPathPrefix = pathConstructor.constructDispatchPath(uncompressedPrefix, "");
+        compressedPathPrefix = pathConstructor.constructDispatchPath(compressedPrefix, "");
 
         for (String path : configuration.keySet())
         {
-            String extendedPath = path.length() == 0
-                    ? pathPrefix
-                    : pathPrefix + path + "/";
-
-            pathToHandler.put(extendedPath, configuration.get(path));
+            AssetRequestHandler handler = configuration.get(path);
 
-            assetPaths.add(extendedPath);
+            addPath(uncompressedPathPrefix, path, handler);
+            addPath(compressedPathPrefix, path, handler);
         }
 
         // Sort by descending length
@@ -84,6 +87,29 @@ public class AssetDispatcher implements Dispatcher
         });
     }
 
+    private void addPath(String prefix, String path, AssetRequestHandler handler)
+    {
+        String extendedPath = buildPath(prefix, path);
+
+        pathToHandler.put(extendedPath, handler);
+
+        assetPaths.add(extendedPath);
+    }
+
+    private String buildPath(String prefix, String path)
+    {
+        // TODO: Not sure when path would be length 0!
+        return path.length() == 0
+                ? prefix
+                : prefix + path + "/";
+    }
+
+    private boolean matchesEitherPrefix(String path)
+    {
+        return path.startsWith(compressedPathPrefix) ||
+                path.startsWith(uncompressedPathPrefix);
+    }
+
     public boolean dispatch(Request request, Response response) throws IOException
     {
         String path = request.getPath();
@@ -91,7 +117,7 @@ public class AssetDispatcher implements Dispatcher
         // Remember that the request path does not include the context path, so we can simply start
         // looking for the asset path prefix right off the bat.
 
-        if (!path.startsWith(pathPrefix))
+        if (!matchesEitherPrefix(path))
         {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aa465fcd/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index ad954c0..797e464 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -76,6 +76,7 @@ public class AssetsModule
         configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
 
         configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
+        configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
     }
 
     // The use of decorators is to allow third-parties to get their own extensions


[03/10] git commit: Fix broken tests

Posted by hl...@apache.org.
Fix broken tests

Remove unwanted access to HttpServletRequest when using the PageTester


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/5a4c0039
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/5a4c0039
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/5a4c0039

Branch: refs/heads/master
Commit: 5a4c0039b4c03920ab0dfda7e3e6339beb591b40
Parents: 154dac9
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:24:22 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:18 2013 +0100

----------------------------------------------------------------------
 .../tapestry5/internal/test/PageTesterModule.java  |   31 ++++++++++++---
 1 files changed, 25 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/5a4c0039/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
index 12455b4..656187e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2007-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,15 +17,14 @@ package org.apache.tapestry5.internal.test;
 import org.apache.tapestry5.internal.services.CookieSink;
 import org.apache.tapestry5.internal.services.CookieSource;
 import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.ObjectLocator;
 import org.apache.tapestry5.ioc.OrderedConfiguration;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
 import org.apache.tapestry5.ioc.annotations.Local;
 import org.apache.tapestry5.ioc.services.ServiceOverride;
-import org.apache.tapestry5.services.MarkupRendererFilter;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.RequestFilter;
-import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 import org.apache.tapestry5.test.PageTester;
 
 /**
@@ -46,7 +45,7 @@ public class PageTesterModule
     @Contribute(ServiceOverride.class)
     public static void setupTestableOverrides(MappedConfiguration<Class, Object> configuration, @Local
     TestableRequest request, @Local
-    TestableResponse response)
+                                              TestableResponse response, final ObjectLocator locator)
     {
         configuration.add(Request.class, request);
         configuration.add(Response.class, response);
@@ -55,6 +54,26 @@ public class PageTesterModule
 
         configuration.add(CookieSink.class, cookies);
         configuration.add(CookieSource.class, cookies);
+
+        // With the significant changes to the handling of assets in 5.4, we introduced a problem:
+        // We were checking at page render time whether to generate URLs for normal or compressed
+        // assets and that peeked at the HttpServletRequest global, which isn't set up by PageTester.
+        // What we're doing here is using a hacked version of that code to force GZip support
+        // on.
+        configuration.add(ResponseCompressionAnalyzer.class, new ResponseCompressionAnalyzer()
+        {
+            @Override
+            public boolean isGZipEnabled(String contentType)
+            {
+                return locator.getObject(CompressionAnalyzer.class, null).isCompressable(contentType);
+            }
+
+            @Override
+            public boolean isGZipSupported()
+            {
+                return true;
+            }
+        });
     }
 
     public static void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration)


[10/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/1a97747f
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/1a97747f
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/1a97747f

Branch: refs/heads/master
Commit: 1a97747f80a41bcac4e60afc1a61ac1ba3a9fba9
Parents: e97c402
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Sun Apr 14 15:41:02 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../ModuleAssetRequestHandlerTest.groovy           |   46 +++-----------
 1 files changed, 10 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1a97747f/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
index 56635c3..eed6325 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTest.groovy
@@ -3,9 +3,6 @@ package org.apache.tapestry5.services.javascript
 import org.apache.tapestry5.internal.services.javascript.ModuleAssetRequestHandler
 import org.apache.tapestry5.ioc.internal.QuietOperationTracker
 import org.apache.tapestry5.ioc.test.TestBase
-import org.apache.tapestry5.services.PathConstructor
-import org.apache.tapestry5.services.Request
-import org.apache.tapestry5.services.Response
 import org.testng.annotations.DataProvider
 import org.testng.annotations.Test
 
@@ -13,57 +10,34 @@ class ModuleAssetRequestHandlerTest extends TestBase {
 
     @Test(dataProvider = "unknownPaths")
     void "invalid extension is ignored"(path) {
-        def pc = newMock PathConstructor
-        
-        def request = newMock Request
-        
-        def response = newMock Response
-        
-        expect(request.getPath()).andReturn path
 
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-
-        replay()
-        
         def handler = new ModuleAssetRequestHandler(null, null, new QuietOperationTracker())
-        
-        assertEquals handler.dispatch(request, response), false
 
-        verify()
+        assertEquals handler.handleAssetRequest(null, null, path), false
     }
 
     @DataProvider
     Object[][] unknownPaths() {
         [
-            "foo/bar.xyz",
-            "foo",
-            "foo/bar",
-            ""
-        ].collect({ it -> ["/modules/$it"] as Object[] }) as Object[][]
+            ["foo/bar.xyz"],
+            ["foo"],
+            ["foo/bar"],
+            [""]
+        ] as Object[][]
     }
 
     @Test
     void "returns false if no module is found"() {
 
-        def pc = newMock PathConstructor
-
         def manager = newMock ModuleManager
-        
-        def request = newMock Request
-        
-        def response = newMock Response
-        
-        expect(request.getPath()).andReturn "/modules/foo/bar.js"
-        
-        expect(pc.constructDispatchPath("modules", "")).andReturn "/modules/"
-        
+
         expect(manager.findResourceForModule("foo/bar")).andReturn null
-        
+
         replay()
 
         def handler = new ModuleAssetRequestHandler(manager, null, new QuietOperationTracker())
-        
-        assertEquals handler.dispatch(request, response), false
+
+        assertEquals handler.handleAssetRequest(null, null, "foo/bar.js"), false
 
         verify()
     }


[08/10] git commit: Correct unit tests broken by recent refactorings

Posted by hl...@apache.org.
Correct unit tests broken by recent refactorings


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/3c0cb494
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/3c0cb494
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/3c0cb494

Branch: refs/heads/master
Commit: 3c0cb4942e78717b0c615564bf08633e55eb23e6
Parents: 1a97747
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Apr 15 21:56:52 2013 +0100
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Apr 15 22:11:19 2013 +0100

----------------------------------------------------------------------
 .../assets/AssetPathConstructorImplTest.groovy     |   67 ++++++++-------
 1 files changed, 35 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3c0cb494/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
index 9065587..02ed33e 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
@@ -1,68 +1,71 @@
 package org.apache.tapestry5.internal.services.assets
 
-import org.apache.tapestry5.ioc.Resource
+import org.apache.tapestry5.internal.services.IdentityAssetPathConverter
 import org.apache.tapestry5.ioc.test.TestBase
 import org.apache.tapestry5.services.BaseURLSource
 import org.apache.tapestry5.services.PathConstructor
 import org.apache.tapestry5.services.Request
 import org.apache.tapestry5.services.assets.AssetChecksumGenerator
+import org.apache.tapestry5.services.assets.CompressionStatus
+import org.apache.tapestry5.services.assets.StreamableResource
 import org.testng.annotations.Test
 
 class AssetPathConstructorImplTest extends TestBase {
 
-  @Test
-  void "construct an asset path with no application folder"() {
+    def pathConverter = new IdentityAssetPathConverter()
 
-    def request = newMock(Request)
+    @Test
+    void "construct an asset path with no application folder"() {
 
-    def baseURLSource = newMock(BaseURLSource)
+        def pc = newMock(PathConstructor)
 
-    def pc = newMock(PathConstructor)
+        def gen = newMock(AssetChecksumGenerator)
 
-    def gen = newMock(AssetChecksumGenerator)
+        def r = newMock(StreamableResource)
 
-    def virtExtra = newMock(Resource)
+        expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
-    def virtb = newMock(Resource)
+        expect(r.compression).andReturn(CompressionStatus.COMPRESSED)
 
-    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(r.checksum).andReturn("abc")
 
-    expect(gen.generateChecksum(virtExtra)).andReturn("abc")
+        replay()
 
-    replay()
+        def apc = new AssetPathConstructorImpl(null, null, false, "assets", "assets.gz", pc, pathConverter)
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", null, pc, pathConverter)
+        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets.gz/virt/abc/extra.png"
 
-    assert apc.constructAssetPath("virt", "extra.png", virtExtra) == "/assets/virt/abc/extra.png"
+        verify()
+    }
 
-    verify()
-  }
+    @Test
+    void "fully qualified path has base URL prepended"() {
+        def request = newMock(Request)
 
-  @Test
-  void "fully qualified path has base URL prepended"() {
-    def request = newMock(Request)
-    def baseURLSource = newMock(BaseURLSource)
+        def baseURLSource = newMock(BaseURLSource)
 
-    def pc = newMock(PathConstructor)
+        def pc = newMock(PathConstructor)
 
-    def gen = newMock(AssetChecksumGenerator)
+        def r = newMock(StreamableResource)
 
-    def r = newMock(Resource)
+        expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
-    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
+        expect(request.secure).andReturn(false)
+        expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
 
-    expect(request.secure).andReturn(false)
-    expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
+        expect(r.compression).andReturn(CompressionStatus.NOT_COMPRESSABLE)
 
-    expect(gen.generateChecksum(r)).andReturn("911")
+        expect(r.checksum).andReturn("abc")
 
-    replay()
+        replay()
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", null, pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", "assets.gz", pc, pathConverter)
 
-    assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/911/icon.gif"
+        assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/abc/icon.gif"
 
-    verify()
+        verify()
 
-  }
+    }
 }