You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2013/11/27 20:27:51 UTC

[1/2] git commit: TAP5-2234: Refreshing the browser in Eclipse on Windows sometimes fails with a locking exception

Updated Branches:
  refs/heads/master d34783f05 -> 067916b6c


TAP5-2234: Refreshing the browser in Eclipse on Windows sometimes fails with a locking exception


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

Branch: refs/heads/master
Commit: d23f8e06687a3ffe5b8d1b0b57e957008cb6e61a
Parents: d34783f
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Nov 27 11:12:42 2013 -0800
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Wed Nov 27 11:12:42 2013 -0800

----------------------------------------------------------------------
 .../internal/webresources/ResourceTransformerFactoryImpl.java    | 4 ++++
 1 file changed, 4 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/d23f8e06/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
index f02d94d..157ffcf 100644
--- a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/ResourceTransformerFactoryImpl.java
@@ -210,6 +210,8 @@ public class ResourceTransformerFactoryImpl implements ResourceTransformerFactor
 
                 compiled.store(is);
 
+                is.close();
+
                 cache.put(source, compiled);
 
                 return compiled.openStream();
@@ -249,6 +251,8 @@ public class ResourceTransformerFactoryImpl implements ResourceTransformerFactor
 
                 TapestryInternalUtils.copy(compiled, bos);
 
+                compiled.close();
+
                 BytestreamCache cache = new BytestreamCache(bos);
 
                 writeToCacheFile(cacheFile, cache.openStream());


[2/2] git commit: Simpliy asset & module paths

Posted by hl...@apache.org.
Simpliy asset & module paths

- compressed assets now have a "z" prefix on their checksum in the URL
- assets served from request path "/assets/"
- modules are now top level ("/modules/", not "/assets/modules/")
- compressed asses are now "/modules.gz/"


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

Branch: refs/heads/master
Commit: 067916b6c95b49195c3f28bebd43f25efe081521
Parents: d23f8e0
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Nov 27 11:27:42 2013 -0800
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Wed Nov 27 11:27:42 2013 -0800

----------------------------------------------------------------------
 .../org/apache/tapestry5/SymbolConstants.java   |  51 ++++----
 .../org/apache/tapestry5/TapestryConstants.java |  11 --
 .../internal/services/AssetDispatcher.java      |  57 +++------
 .../internal/services/ResourceStreamer.java     |   5 +-
 .../internal/services/ResourceStreamerImpl.java |   8 +-
 .../assets/AssetPathConstructorImpl.java        |  51 +++-----
 .../assets/StackAssetRequestHandler.java        |  14 ++-
 .../javascript/ModuleAssetRequestHandler.java   |  93 ---------------
 .../services/javascript/ModuleDispatcher.java   | 117 +++++++++++++++++++
 .../services/javascript/ModuleManagerImpl.java  |  14 ++-
 .../internal/util/VirtualResource.java          |   2 +-
 .../apache/tapestry5/modules/AssetsModule.java  |  17 ++-
 .../tapestry5/modules/JavaScriptModule.java     |  32 +++--
 .../tapestry5/modules/TapestryModule.java       |  13 +--
 .../services/assets/AssetPathConstructor.java   |  15 ---
 .../integration/app1/LibraryTests.groovy        |   2 +-
 .../integration/appfolder/AppFolderTests.groovy |   2 +-
 .../assets/AssetPathConstructorImplTest.groovy  |   8 +-
 .../ModuleAssetRequestHandlerTests.groovy       |  43 -------
 .../javascript/ModuleDispatcherTests.groovy     |  62 ++++++++++
 20 files changed, 305 insertions(+), 312 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 9414917..c101825 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -336,20 +336,23 @@ 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 "asset".  It may contain slashes, but should not begin or end with one. This is the prefix
-     * for uncompressed assets.
+     * It may contain slashes, but should not begin or end with one.
+     * <p/>
+     * The default is "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".
+     * Prefix used for all module resources. This may contain slashes, but should not being or end with one.
+     * Tapestry will create two {@link org.apache.tapestry5.services.Dispatcher}s from this: one for normal
+     * modules, the other for GZip compressed modules (by appending ".gz" to this value).
+     * <p/>
+     * The default is "modules".
      *
      * @since 5.4
      */
-    public static final String COMPRESSED_ASSET_PATH_PREFIX = "tapestry.compressed-asset-path-prefix";
+    public static final String MODULE_PATH_PREFIX = "tapestry.module-path-prefix";
 
     /**
      * Identifies the context path of the application, as determined from {@link javax.servlet.ServletContext#getContextPath()}.
@@ -408,36 +411,36 @@ public class SymbolConstants
      * @since 5.4
      */
     public static final String SESSION_LOCKING_ENABLED = "tapestry.session-locking-enabled";
-    
+
     /**
      * If true (the default), then Tapestry will automatically include the "core" stack in all
      * pages.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2169
+     * @since 5.4
      */
     public static final String INCLUDE_CORE_STACK = "tapestry.include-core-stack";
-    
+
     /**
      * Defines the CSS class that will be given to HTML element (usually a div) &lt;div&gt; generated by
-     * the {@linkplain FormGroup} mixin and the 
+     * the {@linkplain FormGroup} mixin and the
      * {@linkplain BeanEditForm} and {@linkplain BeanEditor}
      * components surrounding the label and the field. The default value is <code>form-group</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_WRAPPER_CSS_CLASS = "tapestry.form-group-wrapper-css-class";
-    
+
     /**
      * Defines the name of the HTML element that will surround the HTML form field generated by
      * the {@linkplain FormGroup} mixin and the {@linkplain BeanEditForm} and {@linkplain BeanEditor}.
      * If this symbol is null or an empty string, no element will be generated surrouding the
      * form field. The default value is the empty string (no wrapping).
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
      * @see #FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS
+     * @since 5.4
      */
     public static final String FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME = "tapestry.form-group-form-field-wrapper-element-name";
 
@@ -446,29 +449,29 @@ public class SymbolConstants
      * the {@linkplain FormGroup} mixin and the {@linkplain BeanEditForm} and {@linkplain BeanEditor}.
      * when {@linkplain FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME} is not set to null or the empty string.
      * The default value is the empty string (no CSS class added).
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS = "tapestry.form-group-form-field-wrapper-element-css-class";
 
     /**
      * Defines the CSS class that will be given to &lt;label&gt; element generated by
-     * the {@linkplain FormGroup} mixin and the 
+     * the {@linkplain FormGroup} mixin and the
      * {@linkplain BeanEditForm} and {@linkplain BeanEditor}
      * components. The default value is <code>control-label</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_LABEL_CSS_CLASS = "tapestry.form-group-label-css-class";
 
     /**
      * Defines the CSS class that will be given to form field components which are
      * {@linkplain AbstractField} subclasses. The default value is <code>form-control</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_FIELD_CSS_CLASS = "tapestry.form-field-css-class";
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 74adf03..83a6606 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -46,15 +46,4 @@ public class TapestryConstants
      * @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/067916b6/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 859680d..760ecb9 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
@@ -14,28 +14,21 @@
 
 package org.apache.tapestry5.internal.services;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletResponse;
-
 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;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.services.AssetRequestDispatcher;
-import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-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.*;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
 /**
  * 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
@@ -60,40 +53,22 @@ public class AssetDispatcher implements Dispatcher
      */
     private final List<String> assetPaths = CollectionFactory.newList();
 
-    private final String uncompressedPathPrefix, compressedPathPrefix;
-
-    private AssetRequestHandler wrap(final boolean compress, final AssetRequestHandler handler)
-    {
-        return new AssetRequestHandler()
-        {
-            public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
-            {
-                request.setAttribute(TapestryConstants.COMPRESS_CONTENT, compress);
-
-                return handler.handleAssetRequest(request, response, extraPath);
-            }
-        };
-    }
+    private final String requestPathPrefix;
 
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
 
                            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                           String uncompressedPrefix,
-
-                           @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
-                           String compressedPrefix)
+                           String assetPathPrefix)
     {
-        uncompressedPathPrefix = pathConstructor.constructDispatchPath(uncompressedPrefix, "");
-        compressedPathPrefix = pathConstructor.constructDispatchPath(compressedPrefix, "");
+        requestPathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
 
         for (String path : configuration.keySet())
         {
             AssetRequestHandler handler = configuration.get(path);
 
-            addPath(uncompressedPathPrefix, path, wrap(false, handler));
-            addPath(compressedPathPrefix, path, wrap(true, handler));
+            addPath(requestPathPrefix, path, handler);
         }
 
         // Sort by descending length
@@ -124,12 +99,6 @@ public class AssetDispatcher implements Dispatcher
                 : 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();
@@ -137,7 +106,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 (!matchesEitherPrefix(path))
+        if (!path.startsWith(requestPathPrefix))
         {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 81027bf..f28e9b1 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
@@ -25,7 +25,7 @@ import java.util.Set;
 
 /**
  * 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},
+ * {@link Resource} (often from the {@link org.apache.tapestry5.internal.services.javascript.ModuleDispatcher},
  * 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).
@@ -66,7 +66,8 @@ public interface ResourceStreamer
     boolean streamResource(Resource resource, String providedChecksum, Set<Options> options) throws IOException;
 
     /**
-     * Streams a resource that has been assembled elsewhere.
+     * Streams a resource that has been assembled elsewhere.  The StreamableResource may reflect either a normal
+     * or a compressed stream, depending on the type of resource and the capabilities of the client.
      *
      * @param resource
      *         content to stream

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 9eff1e8..cbfe264 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,7 +15,6 @@
 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;
@@ -85,8 +84,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
             return true;
         }
 
-        final boolean compress = (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
-
+        final boolean compress = providedChecksum.startsWith("z");
 
         return tracker.perform(String.format("Streaming %s%s", resource, compress ? " (compressed)" : ""), new IOOperation<Boolean>()
         {
@@ -98,7 +96,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
 
                 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
 
-                return streamResource(streamable, providedChecksum, options);
+                return streamResource(streamable, compress ? providedChecksum.substring(1) : providedChecksum, options);
             }
         });
     }
@@ -118,7 +116,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
 
         long lastModified = streamable.getLastModified();
 
-        long ifModifiedSince = 0;
+        long ifModifiedSince;
 
         try
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 09e434f..e8a3231 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
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011, 2012, 2013 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.
@@ -31,7 +31,7 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 {
     private final Request request;
 
-    private final String uncompressedPrefix, compressedPrefix;
+    private final String prefix;
 
     private final BaseURLSource baseURLSource;
 
@@ -48,9 +48,6 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
                                     @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
                                     String uncompressedAssetPrefix,
 
-                                    @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
-                                    String compressedAssetPrefix,
-
                                     PathConstructor pathConstructor,
 
                                     AssetPathConverter pathConverter)
@@ -61,37 +58,13 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
         this.fullyQualified = fullyQualified;
         this.pathConverter = pathConverter;
 
-        uncompressedPrefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
-        compressedPrefix = pathConstructor.constructClientPath(compressedAssetPrefix, "");
+        prefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
     }
 
     public String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException
     {
         assert InternalUtils.isNonBlank(path);
 
-        StringBuilder builder = create(resource.getCompression() == CompressionStatus.COMPRESSED, virtualFolder);
-        builder.append("/");
-
-        builder.append(resource.getChecksum());
-
-        builder.append('/');
-        builder.append(path);
-
-        return finish(builder);
-    }
-
-    public String constructAssetPath(String virtualFolder, boolean compressed)
-    {
-        return finish(create(compressed, virtualFolder));
-    }
-
-    private String finish(StringBuilder builder)
-    {
-        return pathConverter.convertAssetPath(builder.toString());
-    }
-
-    private StringBuilder create(boolean compress, String virtualFolder)
-    {
         assert InternalUtils.isNonBlank(virtualFolder);
 
         StringBuilder builder = new StringBuilder();
@@ -101,8 +74,22 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
             builder.append(baseURLSource.getBaseURL(request.isSecure()));
         }
 
-        builder.append(compress ? compressedPrefix : uncompressedPrefix);
+        builder.append(prefix);
+        builder.append(virtualFolder);
+        builder.append("/");
+
+        // The 'z' prefix indicates a compressed resource.
+
+        if (resource.getCompression() == CompressionStatus.COMPRESSED)
+        {
+            builder.append("z");
+        }
+
+        builder.append(resource.getChecksum());
+        builder.append('/');
+        builder.append(path);
 
-        return builder.append(virtualFolder);
+        return pathConverter.convertAssetPath(builder.toString());
     }
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 487fcf1..c883986 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,7 +14,6 @@
 
 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;
@@ -48,13 +47,12 @@ public class StackAssetRequestHandler implements AssetRequestHandler
     private final JavaScriptStackAssembler javaScriptStackAssembler;
 
     private final JavaScriptStackSource stackSource;
-    private final Request request;
 
     public StackAssetRequestHandler(Logger logger, LocalizationSetter localizationSetter,
                                     ResourceStreamer resourceStreamer,
                                     OperationTracker tracker,
                                     JavaScriptStackAssembler javaScriptStackAssembler,
-                                    JavaScriptStackSource stackSource, Request request)
+                                    JavaScriptStackSource stackSource)
     {
         this.logger = logger;
         this.localizationSetter = localizationSetter;
@@ -62,7 +60,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         this.tracker = tracker;
         this.javaScriptStackAssembler = javaScriptStackAssembler;
         this.stackSource = stackSource;
-        this.request = request;
     }
 
     public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
@@ -92,6 +89,13 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         String localeName = matcher.group(2);
         final String stackName = matcher.group(3);
 
+        final boolean compressed = checksum.startsWith("z");
+
+        if (compressed)
+        {
+            checksum = checksum.substring(1);
+        }
+
         if (stackSource.findStack(stackName) == null)
         {
             logger.warn(String.format("JavaScript stack '%s' not found.", stackName));
@@ -111,8 +115,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                         {
                             public StreamableResource perform() throws IOException
                             {
-                                boolean compressed =
-                                        (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
 
                                 return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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
deleted file mode 100644
index 49d366b..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleAssetRequestHandler.java
+++ /dev/null
@@ -1,93 +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 java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-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;
-
-/**
- * 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;
-    }
-
-    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/067916b6/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
new file mode 100644
index 0000000..a841578
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
@@ -0,0 +1,117 @@
+// 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.  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 ModuleDispatcher implements Dispatcher
+{
+    private final ModuleManager moduleManager;
+
+    private final ResourceStreamer streamer;
+
+    private final OperationTracker tracker;
+
+    private final String requestPrefix;
+
+    private final boolean compress;
+
+    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
+
+    public ModuleDispatcher(ModuleManager moduleManager,
+                            ResourceStreamer streamer,
+                            OperationTracker tracker,
+                            PathConstructor pathConstructor,
+                            String prefix,
+                            boolean compress)
+    {
+        this.moduleManager = moduleManager;
+        this.streamer = streamer;
+        this.tracker = tracker;
+        this.compress = compress;
+
+        requestPrefix = pathConstructor.constructDispatchPath(compress ? prefix + ".gz" : prefix) + "/";
+    }
+
+    public boolean dispatch(Request request, Response response) throws IOException
+    {
+        String path = request.getPath();
+
+        return path.startsWith(requestPrefix) &&
+                handleModuleRequest(path.substring(requestPrefix.length()));
+
+    }
+
+    private boolean handleModuleRequest(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 %s %s",
+                compress ? "compressed module" : "module",
+                moduleName), new IOOperation<Boolean>()
+        {
+            public Boolean perform() throws IOException
+            {
+                Resource resource = moduleManager.findResourceForModule(moduleName);
+
+                if (resource != null)
+                {
+                    // Slightly hacky way of informing the streamer whether to supply the
+                    // compressed or default stream. May need to iterate the API on this a bit.
+                    return streamer.streamResource(resource, compress ? "z" : "", omitExpiration);
+                }
+
+                return false;
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 8e0f3df..26cb24c 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
@@ -26,8 +26,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.ModuleConfigurationCallback;
@@ -57,7 +57,7 @@ public class ModuleManagerImpl implements ModuleManager
 
     private final JSONObject baseConfig;
 
-    private final AssetPathConstructor assetPathConstructor;
+    private final String basePath, compressedBasePath;
 
     public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer,
                              AssetSource assetSource,
@@ -68,12 +68,16 @@ public class ModuleManagerImpl implements ModuleManager
                              boolean compactJSON,
                              @Symbol(SymbolConstants.PRODUCTION_MODE)
                              boolean productionMode,
-                             AssetPathConstructor assetPathConstructor)
+                             @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
+                             String modulePathPrefix,
+                             PathConstructor pathConstructor)
     {
         this.compressionAnalyzer = compressionAnalyzer;
         this.globalMessages = globalMessages;
         this.compactJSON = compactJSON;
-        this.assetPathConstructor = assetPathConstructor;
+
+        basePath = pathConstructor.constructClientPath(modulePathPrefix);
+        compressedBasePath = pathConstructor.constructClientPath(modulePathPrefix + ".gz");
 
         classpathRoot = assetSource.resourceForPath("");
         extensions = CollectionFactory.newSet("js");
@@ -129,7 +133,7 @@ public class ModuleManagerImpl implements ModuleManager
 
     private String getBaseURL()
     {
-        return assetPathConstructor.constructAssetPath("module", compressionAnalyzer.isGZipSupported());
+        return compressionAnalyzer.isGZipSupported() ? compressedBasePath : basePath;
     }
 
     private void addModuleToConfig(JSONObject config, String name, JavaScriptModuleConfiguration module)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 bb1061f..0fc0738 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.ModuleAssetRequestHandler
+ * @see org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
  * @since 5.4
  */
 public abstract class VirtualResource implements Resource

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
index ca928e6..ab2e502 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
@@ -76,8 +76,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, "asset");
-        configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
+        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
 
         configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "${tapestry.asset.root}/bootstrap-3.0.2");
 
@@ -309,4 +308,18 @@ public class AssetsModule
         configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
         configuration.add("AppCatalog", applicationCatalog);
     }
+
+    @Contribute(Dispatcher.class)
+    @Primary
+    public static void setupAssetDispatch(OrderedConfiguration<Dispatcher> configuration,
+                                          @AssetRequestDispatcher
+                                          Dispatcher assetDispatcher)
+    {
+
+        // This goes first because an asset to be streamed may have an file
+        // extension, such as
+        // ".html", that will confuse the later dispatchers.
+
+        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index 9475594..fd095a9 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -19,22 +19,20 @@ import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.DocumentLinker;
+import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.internal.services.javascript.*;
 import org.apache.tapestry5.internal.util.MessageCatalogResource;
-import org.apache.tapestry5.ioc.MappedConfiguration;
-import org.apache.tapestry5.ioc.OrderedConfiguration;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.*;
 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.javascript.*;
@@ -138,7 +136,8 @@ public class JavaScriptModule
 
         configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery-1.9.1.js"));
 
-        if (provider.equals("prototype")) {
+        if (provider.equals("prototype"))
+        {
             configuration.add("jquery-noconflict", StackExtension.library(ROOT + "/jquery-noconflict.js"));
         }
 
@@ -187,10 +186,22 @@ public class JavaScriptModule
     }
 
     @Contribute(Dispatcher.class)
-    @AssetRequestDispatcher
-    public static void provideModuleHandler(MappedConfiguration<String, AssetRequestHandler> configuration)
+    @Primary
+    public static void setupModuleDispatchers(OrderedConfiguration<Dispatcher> configuration,
+                                              ModuleManager moduleManager,
+                                              OperationTracker tracker,
+                                              ResourceStreamer resourceStreamer,
+                                              PathConstructor pathConstructor,
+                                              @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
+                                              String modulePathPrefix)
     {
-        configuration.addInstance("module", ModuleAssetRequestHandler.class);
+        configuration.add("Modules",
+                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor, modulePathPrefix, false),
+                "after:Asset", "before:ComponentEvent");
+
+        configuration.add("ComnpressedModules",
+                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor, modulePathPrefix, true),
+                "after:Modules", "before:ComponentEvent");
     }
 
     /**
@@ -312,9 +323,10 @@ public class JavaScriptModule
 
     @Contribute(SymbolProvider.class)
     @FactoryDefaults
-    public static void declareDefaultJavaScriptInfrastructureProvider(MappedConfiguration<String, Object> configuration)
+    public static void setupFactoryDefaults(MappedConfiguration<String, Object> configuration)
     {
         configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "prototype");
+        configuration.add(SymbolConstants.MODULE_PATH_PREFIX, "modules");
     }
 
     @Contribute(ModuleManager.class)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index f4c9209..b9ff2d8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -1627,8 +1627,6 @@ public final class TapestryModule
      * <dl>
      * <dt>RootPath</dt>
      * <dd>Renders the start page for the "/" request (outdated)</dd>
-     * <dt>Asset</dt>
-     * <dd>Provides access to assets (context, classpath and virtual) via {@link AssetDispatcher}</dd>
      * <dt>PageRender</dt>
      * <dd>Identifies the {@link org.apache.tapestry5.services.PageRenderRequestParameters} and forwards onto
      * {@link PageRenderRequestHandler}</dd>
@@ -1637,10 +1635,7 @@ public final class TapestryModule
      * {@link ComponentEventRequestHandler}</dd>
      * </dl>
      */
-    public static void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration,
-
-                                                  @AssetRequestDispatcher
-                                                  Dispatcher assetDispatcher)
+    public static void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration)
     {
         // Looks for the root path and renders the start page. This is
         // maintained for compatibility
@@ -1649,12 +1644,6 @@ public final class TapestryModule
 
         configuration.addInstance("RootPath", RootPathDispatcher.class, "before:Asset");
 
-        // This goes first because an asset to be streamed may have an file
-        // extension, such as
-        // ".html", that will confuse the later dispatchers.
-
-        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
-
         configuration.addInstance("ComponentEvent", ComponentEventDispatcher.class, "before:PageRender");
 
         configuration.addInstance("PageRender", PageRenderDispatcher.class);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 ffb4d2c..db67493 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
@@ -48,19 +48,4 @@ public interface AssetPathConstructor
     @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may now be thrown")
     String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException;
 
-    /**
-     * 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 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 constructAssetPath(String virtualFolder, boolean compressed);
-
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 07263c4..67a72d8 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 = ~"/asset/lib/alpha/\\w+/pages/tapestry\\.png"
+        def pattern = ~"/assets/lib/alpha/\\w+/pages/tapestry\\.png"
 
         assert pattern.matcher(assetURL).matches()
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 3810816..0ee69ba 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
@@ -56,7 +56,7 @@ class AppFolderTests extends GroovyTapestryCoreTestCase
         // Ony one image on page
         String assetURL = getAttribute("//img/@src")
 
-        assert assetURL.startsWith("/t5app/asset/")
+        assert assetURL.startsWith("/t5app/assets/")
 
         assertDownloadedAsset assetURL, "src/test/appfolder/images/t5-logo.png"
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 02ed33e..b6e4337 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
@@ -24,7 +24,6 @@ class AssetPathConstructorImplTest extends TestBase {
         def r = newMock(StreamableResource)
 
         expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
-        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
         expect(r.compression).andReturn(CompressionStatus.COMPRESSED)
 
@@ -32,9 +31,9 @@ class AssetPathConstructorImplTest extends TestBase {
 
         replay()
 
-        def apc = new AssetPathConstructorImpl(null, null, false, "assets", "assets.gz", pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(null, null, false, "assets", pc, pathConverter)
 
-        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets.gz/virt/abc/extra.png"
+        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets/virt/zabc/extra.png"
 
         verify()
     }
@@ -50,7 +49,6 @@ class AssetPathConstructorImplTest extends TestBase {
         def r = newMock(StreamableResource)
 
         expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
-        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
         expect(request.secure).andReturn(false)
         expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
@@ -61,7 +59,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
         replay()
 
-        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", "assets.gz", pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", pc, pathConverter)
 
         assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/abc/icon.gif"
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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
deleted file mode 100644
index 5b24b1c..0000000
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
+++ /dev/null
@@ -1,43 +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.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/067916b6/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
new file mode 100644
index 0000000..c239026
--- /dev/null
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
@@ -0,0 +1,62 @@
+package org.apache.tapestry5.services.javascript
+
+import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+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 request = newMock Request
+        def pc = newMock PathConstructor
+
+        expect(pc.constructDispatchPath("modules")).andReturn("/modules")
+
+        expect(request.path).andReturn(path)
+
+        replay()
+
+        def handler = new ModuleDispatcher(null, null, new QuietOperationTracker(), pc, "modules", false)
+
+        assert 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 manager = newMock ModuleManager
+        def request = newMock Request
+        def pc = newMock PathConstructor
+
+        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 ModuleDispatcher(manager, null, new QuietOperationTracker(), pc, "modules", false)
+
+        assert handler.dispatch(request, null) == false
+
+        verify()
+    }
+}


[2/2] git commit: Simpliy asset & module paths

Posted by hl...@apache.org.
Simpliy asset & module paths

- compressed assets now have a "z" prefix on their checksum in the URL
- assets served from request path "/assets/"
- modules are now top level ("/modules/", not "/assets/modules/")
- compressed asses are now "/modules.gz/"


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

Branch: refs/heads/master
Commit: 067916b6c95b49195c3f28bebd43f25efe081521
Parents: d23f8e0
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Nov 27 11:27:42 2013 -0800
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Wed Nov 27 11:27:42 2013 -0800

----------------------------------------------------------------------
 .../org/apache/tapestry5/SymbolConstants.java   |  51 ++++----
 .../org/apache/tapestry5/TapestryConstants.java |  11 --
 .../internal/services/AssetDispatcher.java      |  57 +++------
 .../internal/services/ResourceStreamer.java     |   5 +-
 .../internal/services/ResourceStreamerImpl.java |   8 +-
 .../assets/AssetPathConstructorImpl.java        |  51 +++-----
 .../assets/StackAssetRequestHandler.java        |  14 ++-
 .../javascript/ModuleAssetRequestHandler.java   |  93 ---------------
 .../services/javascript/ModuleDispatcher.java   | 117 +++++++++++++++++++
 .../services/javascript/ModuleManagerImpl.java  |  14 ++-
 .../internal/util/VirtualResource.java          |   2 +-
 .../apache/tapestry5/modules/AssetsModule.java  |  17 ++-
 .../tapestry5/modules/JavaScriptModule.java     |  32 +++--
 .../tapestry5/modules/TapestryModule.java       |  13 +--
 .../services/assets/AssetPathConstructor.java   |  15 ---
 .../integration/app1/LibraryTests.groovy        |   2 +-
 .../integration/appfolder/AppFolderTests.groovy |   2 +-
 .../assets/AssetPathConstructorImplTest.groovy  |   8 +-
 .../ModuleAssetRequestHandlerTests.groovy       |  43 -------
 .../javascript/ModuleDispatcherTests.groovy     |  62 ++++++++++
 20 files changed, 305 insertions(+), 312 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 9414917..c101825 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -336,20 +336,23 @@ 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 "asset".  It may contain slashes, but should not begin or end with one. This is the prefix
-     * for uncompressed assets.
+     * It may contain slashes, but should not begin or end with one.
+     * <p/>
+     * The default is "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".
+     * Prefix used for all module resources. This may contain slashes, but should not being or end with one.
+     * Tapestry will create two {@link org.apache.tapestry5.services.Dispatcher}s from this: one for normal
+     * modules, the other for GZip compressed modules (by appending ".gz" to this value).
+     * <p/>
+     * The default is "modules".
      *
      * @since 5.4
      */
-    public static final String COMPRESSED_ASSET_PATH_PREFIX = "tapestry.compressed-asset-path-prefix";
+    public static final String MODULE_PATH_PREFIX = "tapestry.module-path-prefix";
 
     /**
      * Identifies the context path of the application, as determined from {@link javax.servlet.ServletContext#getContextPath()}.
@@ -408,36 +411,36 @@ public class SymbolConstants
      * @since 5.4
      */
     public static final String SESSION_LOCKING_ENABLED = "tapestry.session-locking-enabled";
-    
+
     /**
      * If true (the default), then Tapestry will automatically include the "core" stack in all
      * pages.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2169
+     * @since 5.4
      */
     public static final String INCLUDE_CORE_STACK = "tapestry.include-core-stack";
-    
+
     /**
      * Defines the CSS class that will be given to HTML element (usually a div) &lt;div&gt; generated by
-     * the {@linkplain FormGroup} mixin and the 
+     * the {@linkplain FormGroup} mixin and the
      * {@linkplain BeanEditForm} and {@linkplain BeanEditor}
      * components surrounding the label and the field. The default value is <code>form-group</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_WRAPPER_CSS_CLASS = "tapestry.form-group-wrapper-css-class";
-    
+
     /**
      * Defines the name of the HTML element that will surround the HTML form field generated by
      * the {@linkplain FormGroup} mixin and the {@linkplain BeanEditForm} and {@linkplain BeanEditor}.
      * If this symbol is null or an empty string, no element will be generated surrouding the
      * form field. The default value is the empty string (no wrapping).
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
      * @see #FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS
+     * @since 5.4
      */
     public static final String FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME = "tapestry.form-group-form-field-wrapper-element-name";
 
@@ -446,29 +449,29 @@ public class SymbolConstants
      * the {@linkplain FormGroup} mixin and the {@linkplain BeanEditForm} and {@linkplain BeanEditor}.
      * when {@linkplain FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME} is not set to null or the empty string.
      * The default value is the empty string (no CSS class added).
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS = "tapestry.form-group-form-field-wrapper-element-css-class";
 
     /**
      * Defines the CSS class that will be given to &lt;label&gt; element generated by
-     * the {@linkplain FormGroup} mixin and the 
+     * the {@linkplain FormGroup} mixin and the
      * {@linkplain BeanEditForm} and {@linkplain BeanEditor}
      * components. The default value is <code>control-label</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_GROUP_LABEL_CSS_CLASS = "tapestry.form-group-label-css-class";
 
     /**
      * Defines the CSS class that will be given to form field components which are
      * {@linkplain AbstractField} subclasses. The default value is <code>form-control</code>.
-     * 
-     * @since 5.4
+     *
      * @see https://issues.apache.org/jira/browse/TAP5-2182
+     * @since 5.4
      */
     public static final String FORM_FIELD_CSS_CLASS = "tapestry.form-field-css-class";
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 74adf03..83a6606 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -46,15 +46,4 @@ public class TapestryConstants
      * @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/067916b6/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 859680d..760ecb9 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
@@ -14,28 +14,21 @@
 
 package org.apache.tapestry5.internal.services;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletResponse;
-
 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;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.services.AssetRequestDispatcher;
-import org.apache.tapestry5.services.ClasspathAssetAliasManager;
-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.*;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
 /**
  * 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
@@ -60,40 +53,22 @@ public class AssetDispatcher implements Dispatcher
      */
     private final List<String> assetPaths = CollectionFactory.newList();
 
-    private final String uncompressedPathPrefix, compressedPathPrefix;
-
-    private AssetRequestHandler wrap(final boolean compress, final AssetRequestHandler handler)
-    {
-        return new AssetRequestHandler()
-        {
-            public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
-            {
-                request.setAttribute(TapestryConstants.COMPRESS_CONTENT, compress);
-
-                return handler.handleAssetRequest(request, response, extraPath);
-            }
-        };
-    }
+    private final String requestPathPrefix;
 
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
                            PathConstructor pathConstructor,
 
                            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                           String uncompressedPrefix,
-
-                           @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
-                           String compressedPrefix)
+                           String assetPathPrefix)
     {
-        uncompressedPathPrefix = pathConstructor.constructDispatchPath(uncompressedPrefix, "");
-        compressedPathPrefix = pathConstructor.constructDispatchPath(compressedPrefix, "");
+        requestPathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
 
         for (String path : configuration.keySet())
         {
             AssetRequestHandler handler = configuration.get(path);
 
-            addPath(uncompressedPathPrefix, path, wrap(false, handler));
-            addPath(compressedPathPrefix, path, wrap(true, handler));
+            addPath(requestPathPrefix, path, handler);
         }
 
         // Sort by descending length
@@ -124,12 +99,6 @@ public class AssetDispatcher implements Dispatcher
                 : 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();
@@ -137,7 +106,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 (!matchesEitherPrefix(path))
+        if (!path.startsWith(requestPathPrefix))
         {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 81027bf..f28e9b1 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
@@ -25,7 +25,7 @@ import java.util.Set;
 
 /**
  * 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},
+ * {@link Resource} (often from the {@link org.apache.tapestry5.internal.services.javascript.ModuleDispatcher},
  * 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).
@@ -66,7 +66,8 @@ public interface ResourceStreamer
     boolean streamResource(Resource resource, String providedChecksum, Set<Options> options) throws IOException;
 
     /**
-     * Streams a resource that has been assembled elsewhere.
+     * Streams a resource that has been assembled elsewhere.  The StreamableResource may reflect either a normal
+     * or a compressed stream, depending on the type of resource and the capabilities of the client.
      *
      * @param resource
      *         content to stream

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 9eff1e8..cbfe264 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,7 +15,6 @@
 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;
@@ -85,8 +84,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
             return true;
         }
 
-        final boolean compress = (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
-
+        final boolean compress = providedChecksum.startsWith("z");
 
         return tracker.perform(String.format("Streaming %s%s", resource, compress ? " (compressed)" : ""), new IOOperation<Boolean>()
         {
@@ -98,7 +96,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
 
                 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
 
-                return streamResource(streamable, providedChecksum, options);
+                return streamResource(streamable, compress ? providedChecksum.substring(1) : providedChecksum, options);
             }
         });
     }
@@ -118,7 +116,7 @@ public class ResourceStreamerImpl implements ResourceStreamer
 
         long lastModified = streamable.getLastModified();
 
-        long ifModifiedSince = 0;
+        long ifModifiedSince;
 
         try
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 09e434f..e8a3231 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
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011, 2012, 2013 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.
@@ -31,7 +31,7 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 {
     private final Request request;
 
-    private final String uncompressedPrefix, compressedPrefix;
+    private final String prefix;
 
     private final BaseURLSource baseURLSource;
 
@@ -48,9 +48,6 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
                                     @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
                                     String uncompressedAssetPrefix,
 
-                                    @Symbol(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX)
-                                    String compressedAssetPrefix,
-
                                     PathConstructor pathConstructor,
 
                                     AssetPathConverter pathConverter)
@@ -61,37 +58,13 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
         this.fullyQualified = fullyQualified;
         this.pathConverter = pathConverter;
 
-        uncompressedPrefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
-        compressedPrefix = pathConstructor.constructClientPath(compressedAssetPrefix, "");
+        prefix = pathConstructor.constructClientPath(uncompressedAssetPrefix, "");
     }
 
     public String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException
     {
         assert InternalUtils.isNonBlank(path);
 
-        StringBuilder builder = create(resource.getCompression() == CompressionStatus.COMPRESSED, virtualFolder);
-        builder.append("/");
-
-        builder.append(resource.getChecksum());
-
-        builder.append('/');
-        builder.append(path);
-
-        return finish(builder);
-    }
-
-    public String constructAssetPath(String virtualFolder, boolean compressed)
-    {
-        return finish(create(compressed, virtualFolder));
-    }
-
-    private String finish(StringBuilder builder)
-    {
-        return pathConverter.convertAssetPath(builder.toString());
-    }
-
-    private StringBuilder create(boolean compress, String virtualFolder)
-    {
         assert InternalUtils.isNonBlank(virtualFolder);
 
         StringBuilder builder = new StringBuilder();
@@ -101,8 +74,22 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
             builder.append(baseURLSource.getBaseURL(request.isSecure()));
         }
 
-        builder.append(compress ? compressedPrefix : uncompressedPrefix);
+        builder.append(prefix);
+        builder.append(virtualFolder);
+        builder.append("/");
+
+        // The 'z' prefix indicates a compressed resource.
+
+        if (resource.getCompression() == CompressionStatus.COMPRESSED)
+        {
+            builder.append("z");
+        }
+
+        builder.append(resource.getChecksum());
+        builder.append('/');
+        builder.append(path);
 
-        return builder.append(virtualFolder);
+        return pathConverter.convertAssetPath(builder.toString());
     }
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 487fcf1..c883986 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,7 +14,6 @@
 
 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;
@@ -48,13 +47,12 @@ public class StackAssetRequestHandler implements AssetRequestHandler
     private final JavaScriptStackAssembler javaScriptStackAssembler;
 
     private final JavaScriptStackSource stackSource;
-    private final Request request;
 
     public StackAssetRequestHandler(Logger logger, LocalizationSetter localizationSetter,
                                     ResourceStreamer resourceStreamer,
                                     OperationTracker tracker,
                                     JavaScriptStackAssembler javaScriptStackAssembler,
-                                    JavaScriptStackSource stackSource, Request request)
+                                    JavaScriptStackSource stackSource)
     {
         this.logger = logger;
         this.localizationSetter = localizationSetter;
@@ -62,7 +60,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         this.tracker = tracker;
         this.javaScriptStackAssembler = javaScriptStackAssembler;
         this.stackSource = stackSource;
-        this.request = request;
     }
 
     public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
@@ -92,6 +89,13 @@ public class StackAssetRequestHandler implements AssetRequestHandler
         String localeName = matcher.group(2);
         final String stackName = matcher.group(3);
 
+        final boolean compressed = checksum.startsWith("z");
+
+        if (compressed)
+        {
+            checksum = checksum.substring(1);
+        }
+
         if (stackSource.findStack(stackName) == null)
         {
             logger.warn(String.format("JavaScript stack '%s' not found.", stackName));
@@ -111,8 +115,6 @@ public class StackAssetRequestHandler implements AssetRequestHandler
                         {
                             public StreamableResource perform() throws IOException
                             {
-                                boolean compressed =
-                                        (Boolean) request.getAttribute(TapestryConstants.COMPRESS_CONTENT);
 
                                 return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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
deleted file mode 100644
index 49d366b..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleAssetRequestHandler.java
+++ /dev/null
@@ -1,93 +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 java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-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;
-
-/**
- * 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;
-    }
-
-    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/067916b6/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
new file mode 100644
index 0000000..a841578
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleDispatcher.java
@@ -0,0 +1,117 @@
+// 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.  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 ModuleDispatcher implements Dispatcher
+{
+    private final ModuleManager moduleManager;
+
+    private final ResourceStreamer streamer;
+
+    private final OperationTracker tracker;
+
+    private final String requestPrefix;
+
+    private final boolean compress;
+
+    private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION);
+
+    public ModuleDispatcher(ModuleManager moduleManager,
+                            ResourceStreamer streamer,
+                            OperationTracker tracker,
+                            PathConstructor pathConstructor,
+                            String prefix,
+                            boolean compress)
+    {
+        this.moduleManager = moduleManager;
+        this.streamer = streamer;
+        this.tracker = tracker;
+        this.compress = compress;
+
+        requestPrefix = pathConstructor.constructDispatchPath(compress ? prefix + ".gz" : prefix) + "/";
+    }
+
+    public boolean dispatch(Request request, Response response) throws IOException
+    {
+        String path = request.getPath();
+
+        return path.startsWith(requestPrefix) &&
+                handleModuleRequest(path.substring(requestPrefix.length()));
+
+    }
+
+    private boolean handleModuleRequest(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 %s %s",
+                compress ? "compressed module" : "module",
+                moduleName), new IOOperation<Boolean>()
+        {
+            public Boolean perform() throws IOException
+            {
+                Resource resource = moduleManager.findResourceForModule(moduleName);
+
+                if (resource != null)
+                {
+                    // Slightly hacky way of informing the streamer whether to supply the
+                    // compressed or default stream. May need to iterate the API on this a bit.
+                    return streamer.streamResource(resource, compress ? "z" : "", omitExpiration);
+                }
+
+                return false;
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 8e0f3df..26cb24c 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
@@ -26,8 +26,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.ModuleConfigurationCallback;
@@ -57,7 +57,7 @@ public class ModuleManagerImpl implements ModuleManager
 
     private final JSONObject baseConfig;
 
-    private final AssetPathConstructor assetPathConstructor;
+    private final String basePath, compressedBasePath;
 
     public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer,
                              AssetSource assetSource,
@@ -68,12 +68,16 @@ public class ModuleManagerImpl implements ModuleManager
                              boolean compactJSON,
                              @Symbol(SymbolConstants.PRODUCTION_MODE)
                              boolean productionMode,
-                             AssetPathConstructor assetPathConstructor)
+                             @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
+                             String modulePathPrefix,
+                             PathConstructor pathConstructor)
     {
         this.compressionAnalyzer = compressionAnalyzer;
         this.globalMessages = globalMessages;
         this.compactJSON = compactJSON;
-        this.assetPathConstructor = assetPathConstructor;
+
+        basePath = pathConstructor.constructClientPath(modulePathPrefix);
+        compressedBasePath = pathConstructor.constructClientPath(modulePathPrefix + ".gz");
 
         classpathRoot = assetSource.resourceForPath("");
         extensions = CollectionFactory.newSet("js");
@@ -129,7 +133,7 @@ public class ModuleManagerImpl implements ModuleManager
 
     private String getBaseURL()
     {
-        return assetPathConstructor.constructAssetPath("module", compressionAnalyzer.isGZipSupported());
+        return compressionAnalyzer.isGZipSupported() ? compressedBasePath : basePath;
     }
 
     private void addModuleToConfig(JSONObject config, String name, JavaScriptModuleConfiguration module)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 bb1061f..0fc0738 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.ModuleAssetRequestHandler
+ * @see org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
  * @since 5.4
  */
 public abstract class VirtualResource implements Resource

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
index ca928e6..ab2e502 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
@@ -76,8 +76,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, "asset");
-        configuration.add(SymbolConstants.COMPRESSED_ASSET_PATH_PREFIX, "${tapestry.asset-path-prefix}.gz");
+        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
 
         configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "${tapestry.asset.root}/bootstrap-3.0.2");
 
@@ -309,4 +308,18 @@ public class AssetsModule
         configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
         configuration.add("AppCatalog", applicationCatalog);
     }
+
+    @Contribute(Dispatcher.class)
+    @Primary
+    public static void setupAssetDispatch(OrderedConfiguration<Dispatcher> configuration,
+                                          @AssetRequestDispatcher
+                                          Dispatcher assetDispatcher)
+    {
+
+        // This goes first because an asset to be streamed may have an file
+        // extension, such as
+        // ".html", that will confuse the later dispatchers.
+
+        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index 9475594..fd095a9 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -19,22 +19,20 @@ import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.DocumentLinker;
+import org.apache.tapestry5.internal.services.ResourceStreamer;
 import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.internal.services.javascript.*;
 import org.apache.tapestry5.internal.util.MessageCatalogResource;
-import org.apache.tapestry5.ioc.MappedConfiguration;
-import org.apache.tapestry5.ioc.OrderedConfiguration;
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.*;
 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.javascript.*;
@@ -138,7 +136,8 @@ public class JavaScriptModule
 
         configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery-1.9.1.js"));
 
-        if (provider.equals("prototype")) {
+        if (provider.equals("prototype"))
+        {
             configuration.add("jquery-noconflict", StackExtension.library(ROOT + "/jquery-noconflict.js"));
         }
 
@@ -187,10 +186,22 @@ public class JavaScriptModule
     }
 
     @Contribute(Dispatcher.class)
-    @AssetRequestDispatcher
-    public static void provideModuleHandler(MappedConfiguration<String, AssetRequestHandler> configuration)
+    @Primary
+    public static void setupModuleDispatchers(OrderedConfiguration<Dispatcher> configuration,
+                                              ModuleManager moduleManager,
+                                              OperationTracker tracker,
+                                              ResourceStreamer resourceStreamer,
+                                              PathConstructor pathConstructor,
+                                              @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
+                                              String modulePathPrefix)
     {
-        configuration.addInstance("module", ModuleAssetRequestHandler.class);
+        configuration.add("Modules",
+                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor, modulePathPrefix, false),
+                "after:Asset", "before:ComponentEvent");
+
+        configuration.add("ComnpressedModules",
+                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor, modulePathPrefix, true),
+                "after:Modules", "before:ComponentEvent");
     }
 
     /**
@@ -312,9 +323,10 @@ public class JavaScriptModule
 
     @Contribute(SymbolProvider.class)
     @FactoryDefaults
-    public static void declareDefaultJavaScriptInfrastructureProvider(MappedConfiguration<String, Object> configuration)
+    public static void setupFactoryDefaults(MappedConfiguration<String, Object> configuration)
     {
         configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "prototype");
+        configuration.add(SymbolConstants.MODULE_PATH_PREFIX, "modules");
     }
 
     @Contribute(ModuleManager.class)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index f4c9209..b9ff2d8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -1627,8 +1627,6 @@ public final class TapestryModule
      * <dl>
      * <dt>RootPath</dt>
      * <dd>Renders the start page for the "/" request (outdated)</dd>
-     * <dt>Asset</dt>
-     * <dd>Provides access to assets (context, classpath and virtual) via {@link AssetDispatcher}</dd>
      * <dt>PageRender</dt>
      * <dd>Identifies the {@link org.apache.tapestry5.services.PageRenderRequestParameters} and forwards onto
      * {@link PageRenderRequestHandler}</dd>
@@ -1637,10 +1635,7 @@ public final class TapestryModule
      * {@link ComponentEventRequestHandler}</dd>
      * </dl>
      */
-    public static void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration,
-
-                                                  @AssetRequestDispatcher
-                                                  Dispatcher assetDispatcher)
+    public static void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration)
     {
         // Looks for the root path and renders the start page. This is
         // maintained for compatibility
@@ -1649,12 +1644,6 @@ public final class TapestryModule
 
         configuration.addInstance("RootPath", RootPathDispatcher.class, "before:Asset");
 
-        // This goes first because an asset to be streamed may have an file
-        // extension, such as
-        // ".html", that will confuse the later dispatchers.
-
-        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
-
         configuration.addInstance("ComponentEvent", ComponentEventDispatcher.class, "before:PageRender");
 
         configuration.addInstance("PageRender", PageRenderDispatcher.class);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 ffb4d2c..db67493 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
@@ -48,19 +48,4 @@ public interface AssetPathConstructor
     @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may now be thrown")
     String constructAssetPath(String virtualFolder, String path, StreamableResource resource) throws IOException;
 
-    /**
-     * 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 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 constructAssetPath(String virtualFolder, boolean compressed);
-
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 07263c4..67a72d8 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 = ~"/asset/lib/alpha/\\w+/pages/tapestry\\.png"
+        def pattern = ~"/assets/lib/alpha/\\w+/pages/tapestry\\.png"
 
         assert pattern.matcher(assetURL).matches()
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 3810816..0ee69ba 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
@@ -56,7 +56,7 @@ class AppFolderTests extends GroovyTapestryCoreTestCase
         // Ony one image on page
         String assetURL = getAttribute("//img/@src")
 
-        assert assetURL.startsWith("/t5app/asset/")
+        assert assetURL.startsWith("/t5app/assets/")
 
         assertDownloadedAsset assetURL, "src/test/appfolder/images/t5-logo.png"
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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 02ed33e..b6e4337 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
@@ -24,7 +24,6 @@ class AssetPathConstructorImplTest extends TestBase {
         def r = newMock(StreamableResource)
 
         expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
-        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
         expect(r.compression).andReturn(CompressionStatus.COMPRESSED)
 
@@ -32,9 +31,9 @@ class AssetPathConstructorImplTest extends TestBase {
 
         replay()
 
-        def apc = new AssetPathConstructorImpl(null, null, false, "assets", "assets.gz", pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(null, null, false, "assets", pc, pathConverter)
 
-        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets.gz/virt/abc/extra.png"
+        assert apc.constructAssetPath("virt", "extra.png", r) == "/assets/virt/zabc/extra.png"
 
         verify()
     }
@@ -50,7 +49,6 @@ class AssetPathConstructorImplTest extends TestBase {
         def r = newMock(StreamableResource)
 
         expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
-        expect(pc.constructClientPath("assets.gz", "")).andReturn("/assets.gz/")
 
         expect(request.secure).andReturn(false)
         expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
@@ -61,7 +59,7 @@ class AssetPathConstructorImplTest extends TestBase {
 
         replay()
 
-        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", "assets.gz", pc, pathConverter)
+        def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", pc, pathConverter)
 
         assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/abc/icon.gif"
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/067916b6/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
deleted file mode 100644
index 5b24b1c..0000000
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleAssetRequestHandlerTests.groovy
+++ /dev/null
@@ -1,43 +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.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/067916b6/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
new file mode 100644
index 0000000..c239026
--- /dev/null
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/services/javascript/ModuleDispatcherTests.groovy
@@ -0,0 +1,62 @@
+package org.apache.tapestry5.services.javascript
+
+import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher
+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 request = newMock Request
+        def pc = newMock PathConstructor
+
+        expect(pc.constructDispatchPath("modules")).andReturn("/modules")
+
+        expect(request.path).andReturn(path)
+
+        replay()
+
+        def handler = new ModuleDispatcher(null, null, new QuietOperationTracker(), pc, "modules", false)
+
+        assert 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 manager = newMock ModuleManager
+        def request = newMock Request
+        def pc = newMock PathConstructor
+
+        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 ModuleDispatcher(manager, null, new QuietOperationTracker(), pc, "modules", false)
+
+        assert handler.dispatch(request, null) == false
+
+        verify()
+    }
+}