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/03/15 00:09:18 UTC

[7/14] git commit: Initial changes to support per-resource checksums on normal assets

Initial changes to support per-resource checksums on normal assets


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

Branch: refs/heads/master
Commit: 8fbbd59437c0153ce17f3373e7eaa2ae88bbc6f5
Parents: 2fc96ca
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Mar 6 17:21:02 2013 -0800
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Thu Mar 14 13:41:11 2013 -0700

----------------------------------------------------------------------
 54_RELEASE_NOTES.txt                               |    6 +-
 .../internal/services/AssetDispatcher.java         |   27 +--
 .../services/ClasspathAssetAliasManagerImpl.java   |    9 +-
 .../internal/services/ClasspathAssetFactory.java   |    4 +-
 .../internal/services/ContextAssetFactory.java     |    4 +-
 .../assets/AssetChecksumGeneratorImpl.java         |   92 +++++++++
 .../services/assets/AssetPathConstructorImpl.java  |   67 +++-----
 .../internal/services/assets/ChecksumPath.java     |   59 ++++++
 .../assets/ClasspathAssetRequestHandler.java       |   22 +--
 .../assets/ContextAssetRequestHandler.java         |   14 +-
 .../JavaScriptStackPathConstructorImpl.java        |    7 +-
 .../services/ClasspathAssetAliasManager.java       |   29 ++--
 .../apache/tapestry5/services/TapestryModule.java  |  126 -------------
 .../services/assets/AssetChecksumGenerator.java    |   37 ++++
 .../services/assets/AssetPathConstructor.java      |   17 +-
 .../services/assets/AssetRequestHandler.java       |   26 +--
 .../tapestry5/services/assets/AssetsModule.java    |  143 ++++++++++++++-
 .../apache/tapestry5/test/TapestryTestCase.java    |   10 +-
 .../assets/AssetPathConstructorImplTest.groovy     |   62 +++----
 .../ClasspathAssetAliasManagerImplTest.java        |   43 ++++-
 .../services/ClasspathAssetFactoryTest.java        |    8 +-
 .../internal/services/ContextAssetFactoryTest.java |  101 +---------
 .../assets/ContextAssetRequestHandlerTest.java     |    5 +-
 23 files changed, 505 insertions(+), 413 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/54_RELEASE_NOTES.txt
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.txt b/54_RELEASE_NOTES.txt
index 13444a3..8c369c6 100644
--- a/54_RELEASE_NOTES.txt
+++ b/54_RELEASE_NOTES.txt
@@ -180,4 +180,8 @@ The OperationTracker interface has had a new method added, for performing an IO
 Tapestry now uses a lock on access to the HttpSession; a shared read lock is acquired when reading session attribute
 names; an exclusive write lock is acquired when reading or writing session attributes. Locks, once acquired, are kept
 until the end of the request. A new configuration symbol can be used to turn this feature off, reverting to Tapestry
-5.3 behavior.
\ No newline at end of file
+5.3 behavior.
+
+## AssetPathConstructor
+
+The interface for AssetPathConstructor was changed incompatibly, to allow for individual asset checksums.

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/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 fe33e2b..0f64f73 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
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2006-2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,15 +15,11 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.services.AssetRequestDispatcher;
 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.ClasspathAssetAliasManager;
-import org.apache.tapestry5.services.Dispatcher;
-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;
@@ -59,28 +55,21 @@ public class AssetDispatcher implements Dispatcher
 
     public AssetDispatcher(Map<String, AssetRequestHandler> configuration,
 
-                           @Symbol(SymbolConstants.APPLICATION_VERSION)
-                           String applicationVersion,
-
                            @Symbol(SymbolConstants.APPLICATION_FOLDER)
                            String applicationFolder,
 
+                           PathConstructor pathConstructor,
+
                            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
                            String assetPathPrefix)
     {
-        StringBuilder pathPrefix = new StringBuilder("/");
-
-        if (! applicationFolder.equals("")) {
-            pathPrefix.append(applicationFolder).append("/");
-        }
-
-        pathPrefix.append(assetPathPrefix).append("/").append(applicationVersion).append("/");
-
-        this.pathPrefix = pathPrefix.toString();
+        pathPrefix = pathConstructor.constructDispatchPath(assetPathPrefix, "");
 
         for (String path : configuration.keySet())
         {
-            String extendedPath = this.pathPrefix + path + "/";
+            String extendedPath = path.length() == 0
+                    ? pathPrefix
+                    : pathPrefix + path + "/";
 
             pathToHandler.put(extendedPath, configuration.get(path));
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
index 3a76ebe..69cb15c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2009, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2009, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
@@ -90,8 +91,10 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
 
     }
 
-    public String toClientURL(String resourcePath)
+    public String toClientURL(Resource resource)
     {
+        String resourcePath = resource.getPath();
+
         for (String pathPrefix : sortedPathPrefixes)
         {
             if (resourcePath.startsWith(pathPrefix))
@@ -100,7 +103,7 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
 
                 String virtualPath = resourcePath.substring(pathPrefix.length() + 1);
 
-                return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath);
+                return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath, resource);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
index 8f298ab..de96d0f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetFactory.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -76,7 +76,7 @@ public class ClasspathAssetFactory implements AssetFactory
             path = path.substring(0, lastdotx + 1) + digestManager.getDigest(resource) + path.substring(lastdotx);
         }
 
-        return aliasManager.toClientURL(path);
+        return aliasManager.toClientURL(resource);
     }
 
     public Asset createAsset(Resource resource)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
index 0a97214..b7e41e2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 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.
@@ -49,7 +49,7 @@ public class ContextAssetFactory implements AssetFactory
 
     public Asset createAsset(Resource resource)
     {
-        String defaultPath = assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath());
+        String defaultPath = assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath(), resource);
 
         if (invariant)
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
new file mode 100644
index 0000000..0ebbcd7
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
@@ -0,0 +1,92 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+public class AssetChecksumGeneratorImpl implements AssetChecksumGenerator
+{
+    private final static int BUFFER_SIZE = 4096;
+
+    private final StreamableResourceSource streamableResourceSource;
+
+    private final ResourceChangeTracker tracker;
+
+    public AssetChecksumGeneratorImpl(StreamableResourceSource streamableResourceSource, ResourceChangeTracker tracker)
+    {
+        this.streamableResourceSource = streamableResourceSource;
+        this.tracker = tracker;
+    }
+
+    public String generateChecksum(Resource resource) throws IOException
+    {
+        // TODO: Caching, and cache invalidation.
+
+        StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, StreamableResourceProcessing.COMPRESSION_DISABLED,
+                tracker);
+
+        return toChecksum(streamable.openStream());
+    }
+
+    private String toChecksum(InputStream is) throws IOException
+    {
+        try
+        {
+            MessageDigest digest = MessageDigest.getInstance("MD5");
+
+            digestStream(digest, is);
+
+            is.close();
+
+            byte[] bytes = digest.digest();
+            char[] encoded = Hex.encodeHex(bytes);
+
+            return new String(encoded);
+        } catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        } finally
+        {
+            InternalUtils.close(is);
+        }
+    }
+
+    private void digestStream(MessageDigest digest, InputStream stream) throws IOException
+    {
+        byte[] buffer = new byte[BUFFER_SIZE];
+
+        while (true)
+        {
+            int length = stream.read(buffer);
+
+            if (length < 0)
+            {
+                return;
+            }
+
+            digest.update(buffer, 0, length);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/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 b9fddbd..159c854 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 The Apache Software Foundation
+// Copyright 2010, 2011, 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.
@@ -15,12 +15,17 @@
 package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.BaseURLSource;
+import org.apache.tapestry5.services.PathConstructor;
 import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
 
+import java.io.IOException;
+
 public class AssetPathConstructorImpl implements AssetPathConstructor
 {
     private final Request request;
@@ -29,64 +34,33 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 
     private final BaseURLSource baseURLSource;
 
+    private final AssetChecksumGenerator assetChecksumGenerator;
+
     private final boolean fullyQualified;
 
     public AssetPathConstructorImpl(Request request,
                                     BaseURLSource baseURLSource,
 
-                                    @Symbol(SymbolConstants.CONTEXT_PATH)
-                                    String contextPath,
-
-                                    @Symbol(SymbolConstants.APPLICATION_VERSION)
-                                    String applicationVersion,
-
-                                    @Symbol(SymbolConstants.APPLICATION_FOLDER)
-                                    String applicationFolder,
-
                                     @Symbol(SymbolConstants.ASSET_URL_FULL_QUALIFIED)
                                     boolean fullyQualified,
 
                                     @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
-                                    String assetPathPrefix)
+                                    String assetPathPrefix,
+
+                                    PathConstructor pathConstructor,
+
+                                    AssetChecksumGenerator assetChecksumGenerator)
     {
         this.request = request;
         this.baseURLSource = baseURLSource;
 
         this.fullyQualified = fullyQualified;
+        this.assetChecksumGenerator = assetChecksumGenerator;
 
-        StringBuilder prefix = new StringBuilder();
-
-        boolean needsSlash = false;
-
-        if (contextPath.length() == 0) {
-            prefix.append("/");
-        }
-        else {
-            prefix.append(contextPath);
-            needsSlash = true;
-        }
-
-        if (!applicationFolder.equals("")) {
-
-            if (needsSlash) {
-                prefix.append("/");
-            }
-
-            prefix.append(applicationFolder).append("/");
-
-            needsSlash = false;
-        }
-
-        if (needsSlash) {
-            prefix.append("/");
-        }
-
-        prefix.append(assetPathPrefix).append("/").append(applicationVersion).append("/");
-
-        this.prefix = prefix.toString();
+        prefix = pathConstructor.constructClientPath(assetPathPrefix, "");
     }
 
-    public String constructAssetPath(String virtualFolder, String path)
+    public String constructAssetPath(String virtualFolder, String path, Resource resource)
     {
         assert InternalUtils.isNonBlank(virtualFolder);
         assert path != null;
@@ -100,6 +74,15 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
 
         builder.append(prefix);
         builder.append(virtualFolder);
+        builder.append("/");
+
+        try
+        {
+            builder.append(assetChecksumGenerator.generateChecksum(resource));
+        } catch (IOException ex)
+        {
+            throw new RuntimeException(ex);
+        }
 
         if (InternalUtils.isNonBlank(path))
         {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
new file mode 100644
index 0000000..8155a01
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ChecksumPath.java
@@ -0,0 +1,59 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.tapestry5.internal.services.ResourceStreamer;
+import org.apache.tapestry5.ioc.Resource;
+
+import java.io.IOException;
+
+/**
+ * Utility used by {@link org.apache.tapestry5.services.assets.AssetRequestHandler} implementations
+ * where the first folder in the extra path is actually a computed checksum of the resource's content.
+ *
+ * @since 5.4
+ */
+public class ChecksumPath {
+  public final String checksum;
+
+  public final String resourcePath;
+
+  private final ResourceStreamer streamer;
+
+  public ChecksumPath(ResourceStreamer streamer, String baseFolder, String extraPath) {
+    this.streamer = streamer;
+    int slashx = extraPath.indexOf('/');
+
+    checksum = extraPath.substring(0, slashx);
+
+    String morePath = extraPath.substring(slashx + 1);
+
+    resourcePath = baseFolder == null
+        ? morePath
+        : baseFolder + "/" + morePath;
+  }
+
+  public boolean stream(Resource resource) throws IOException {
+    if (resource == null) {
+      return false;
+    }
+
+    // TODO: Handle incorrect checksum ... maybe with a redirect?
+
+    streamer.streamResource(resource);
+
+    return true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
index 76d4e06..1441010 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ClasspathAssetRequestHandler.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,20 +14,19 @@
 
 package org.apache.tapestry5.internal.services.assets;
 
-import java.io.IOException;
-
 import org.apache.tapestry5.internal.services.AssetResourceLocator;
 import org.apache.tapestry5.internal.services.ResourceStreamer;
-import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 
+import java.io.IOException;
+
 /**
  * A handler for asset requests for classpath assets (within a specific folder).
  * Each mapping of the {@link ClasspathAssetAliasManager} gets one of these.
- * 
+ *
  * @since 5.2.0
  */
 public class ClasspathAssetRequestHandler implements AssetRequestHandler
@@ -39,7 +38,7 @@ public class ClasspathAssetRequestHandler implements AssetRequestHandler
     private final String baseFolder;
 
     public ClasspathAssetRequestHandler(ResourceStreamer streamer, AssetResourceLocator assetResourceLocator,
-            String baseFolder)
+                                        String baseFolder)
     {
         this.streamer = streamer;
         this.assetResourceLocator = assetResourceLocator;
@@ -48,15 +47,8 @@ public class ClasspathAssetRequestHandler implements AssetRequestHandler
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        String assetPath = baseFolder + "/" + extraPath;
-
-        Resource resource = assetResourceLocator.findClasspathResourceForPath(assetPath);
-
-        if (resource == null)
-            return false;
-
-        streamer.streamResource(resource);
+        ChecksumPath path = new ChecksumPath(streamer, baseFolder, extraPath);
 
-        return true;
+        return path.stream(assetResourceLocator.findClasspathResourceForPath(path.resourcePath));
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
index 3fcadbb..824c824 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandler.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -45,14 +45,14 @@ public class ContextAssetRequestHandler implements AssetRequestHandler
 
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
-        if (illegal.matcher(extraPath).matches())
-            return false;
-
-        Resource resource = rootContextResource.forFile(extraPath);
+        ChecksumPath path = new ChecksumPath(resourceStreamer, null, extraPath);
 
-        resourceStreamer.streamResource(resource);
+        if (illegal.matcher(path.resourcePath).matches())
+        {
+            return false;
+        }
 
-        return true;
+        return path.stream(rootContextResource.forFile(path.resourcePath));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index 1363e56..02b0822 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -80,7 +80,10 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
     {
         String path = String.format("%s/%s.js", threadLocale.getLocale().toString(), stackName);
 
-        String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path);
+        // TODO: Come up with a virtual Resource that represents the actual combined contents. This may involve
+        // looping through the StreamableResourceSource and wrapping the result as a VirtualResource.
+
+        String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, null);
 
         return CollectionFactory.newList(stackURL);
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
index fbcfaa7..e85234e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ClasspathAssetAliasManager.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
+// Copyright 2006, 2008, 2010, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
 
 package org.apache.tapestry5.services;
 
-import java.util.Map;
-
 import org.apache.tapestry5.internal.services.assets.ClasspathAssetRequestHandler;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
 import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
 
+import java.util.Map;
+
 /**
  * Used as part of the support for classpath {@link org.apache.tapestry5.Asset}s, to convert the Asset's
  * {@link org.apache.tapestry5.ioc.Resource} to a URL that can be accessed by the client. The asset path, within the
@@ -28,15 +30,15 @@ import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
  * Service configuration is a map from folder aliases (short names) to complete paths. Names should not start or end end
  * with a slash. Generally, an alias should be a single name (not contain a slash). Paths should also not start or end
  * with a slash. An example mapping would be <code>mylib</code> to <code>com/example/mylib</code>.
- * <p>
+ * <p/>
  * As originally envisioned, this service would simply <em>optimize</em> classpath assets, allowing the URL path for
  * such assets to be shortened (and have a version number added, important for caching); thus the word "alias" makes
  * sense ... it was responsible for creating an "alias" URL shorter than the default "classpath" URL.
- * <p>
+ * <p/>
  * Starting in Tapestry 5.2, this changed; all classpath assets <strong>must</strong> be "aliased" to a shorter URL
  * path. Any URL that can not be shortened is now rejected. This simplifies creating new libraries, but also helps with
  * security concerns, as it limits which portions of the classpath can <em>ever</em> be exposed to the user agent.
- * <p>
+ * <p/>
  * Tapestry automatically contributes a number of mappings: for the application root package itself (as alias "app") and
  * for each library (via {@link ComponentClassResolver#getFolderToPackageMapping()});
  */
@@ -46,20 +48,23 @@ public interface ClasspathAssetAliasManager
     /**
      * Takes a resource path to a classpath resource and adds the asset path prefix to the path. May also convert part
      * of the path to an alias (based on the manager's configuration).
-     * 
-     * @param resourcePath
-     *            resource path on the classpath (with no leading slash)
+     * <p/>
+     * Note: this method's signature changed incompatibly in Tapestry 5.4.
+     *
+     * @param resource
+     *         classpath resource
      * @return URL ready to send to the client
      */
-    String toClientURL(String resourcePath);
+    @IncompatibleChange(release = "5.4", details = "parameter changed from String to Resource")
+    String toClientURL(Resource resource);
 
     /**
      * Returns the mappings used by the service: the keys are the folder aliases (i.e, "corelib")
      * and the values are the corresponding paths (i.e., "org/apache/tapestry5/corelib"). This
      * exists primarily so that {@link ClasspathAssetRequestHandler}s can be created automatically
      * for each mapping.
-     * 
+     *
      * @since 5.2.0
-     **/
+     */
     Map<String, String> getMappings();
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
index 3eebd3a..786f61d 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
@@ -39,10 +39,6 @@ import org.apache.tapestry5.internal.services.*;
 import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateFilter;
 import org.apache.tapestry5.internal.services.ajax.AjaxResponseRendererImpl;
 import org.apache.tapestry5.internal.services.ajax.MultiZoneUpdateEventResultProcessor;
-import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
-import org.apache.tapestry5.internal.services.assets.ClasspathAssetRequestHandler;
-import org.apache.tapestry5.internal.services.assets.ContextAssetRequestHandler;
-import org.apache.tapestry5.internal.services.assets.StackAssetRequestHandler;
 import org.apache.tapestry5.internal.services.linktransform.LinkTransformerImpl;
 import org.apache.tapestry5.internal.services.linktransform.LinkTransformerInterceptor;
 import org.apache.tapestry5.internal.services.messages.ClientLocalizationMessageResource;
@@ -75,14 +71,11 @@ import org.apache.tapestry5.runtime.ComponentResourcesAware;
 import org.apache.tapestry5.runtime.RenderCommand;
 import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
-import org.apache.tapestry5.services.assets.AssetPathConstructor;
-import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.assets.AssetsModule;
 import org.apache.tapestry5.services.compatibility.CompatibilityModule;
 import org.apache.tapestry5.services.dynamic.DynamicTemplate;
 import org.apache.tapestry5.services.dynamic.DynamicTemplateParser;
 import org.apache.tapestry5.services.javascript.JavaScriptModule;
-import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.ModuleManager;
 import org.apache.tapestry5.services.linktransform.ComponentEventLinkTransformer;
 import org.apache.tapestry5.services.linktransform.LinkTransformer;
@@ -309,7 +302,6 @@ public final class TapestryModule
 
     public static void bind(ServiceBinder binder)
     {
-        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
         binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
         binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
         binder.bind(ApplicationStatePersistenceStrategySource.class,
@@ -317,7 +309,6 @@ public final class TapestryModule
         binder.bind(BindingSource.class, BindingSourceImpl.class);
         binder.bind(FieldValidatorSource.class, FieldValidatorSourceImpl.class);
         binder.bind(ApplicationGlobals.class, ApplicationGlobalsImpl.class);
-        binder.bind(AssetSource.class, AssetSourceImpl.class);
         binder.bind(Cookies.class, CookiesImpl.class);
         binder.bind(FieldValidatorDefaultSource.class, FieldValidatorDefaultSourceImpl.class);
         binder.bind(RequestGlobals.class, RequestGlobalsImpl.class);
@@ -354,7 +345,6 @@ public final class TapestryModule
         binder.bind(ContextPathEncoder.class, ContextPathEncoderImpl.class);
         binder.bind(ApplicationStatePersistenceStrategy.class, SessionApplicationStatePersistenceStrategy.class).withSimpleId();
         binder.bind(TapestrySessionFactory.class, TapestrySessionFactoryImpl.class);
-        binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
         binder.bind(NumericTranslatorSupport.class);
         binder.bind(ClientDataEncoder.class, ClientDataEncoderImpl.class);
         binder.bind(ComponentEventLinkEncoder.class, ComponentEventLinkEncoderImpl.class);
@@ -363,7 +353,6 @@ public final class TapestryModule
         binder.bind(PropertiesFileParser.class, PropertiesFileParserImpl.class);
         binder.bind(PageActivator.class, PageActivatorImpl.class);
         binder.bind(Dispatcher.class, AssetDispatcher.class).withSimpleId();
-        binder.bind(AssetPathConstructor.class, AssetPathConstructorImpl.class);
         binder.bind(TranslatorAlternatesSource.class, TranslatorAlternatesSourceImpl.class);
         binder.bind(MetaWorker.class, MetaWorkerImpl.class);
         binder.bind(LinkTransformer.class, LinkTransformerImpl.class);
@@ -374,7 +363,6 @@ public final class TapestryModule
         binder.bind(ValidationDecoratorFactory.class, ValidationDecoratorFactoryImpl.class);
         binder.bind(PropertyConduitSource.class, PropertyConduitSourceImpl.class);
         binder.bind(ClientWhitelist.class, ClientWhitelistImpl.class);
-        binder.bind(AssetFactory.class, ClasspathAssetFactory.class).withSimpleId();
         binder.bind(MetaDataLocator.class, MetaDataLocatorImpl.class);
         binder.bind(ComponentClassCache.class, ComponentClassCacheImpl.class);
         binder.bind(PageActivationContextCollector.class, PageActivationContextCollectorImpl.class);
@@ -441,76 +429,6 @@ public final class TapestryModule
         configuration.add(BindingConstants.SYMBOL, symbolBindingFactory);
     }
 
-    @Contribute(ClasspathAssetAliasManager.class)
-    public static void addMappingsForLibraryVirtualFolders(MappedConfiguration<String, String> configuration,
-                                                           ComponentClassResolver resolver)
-    {
-        // Each library gets a mapping or its folder automatically
-
-        Map<String, String> folderToPackageMapping = resolver.getFolderToPackageMapping();
-
-        for (String folder : folderToPackageMapping.keySet())
-        {
-            // This is the 5.3 version, which is still supported:
-            configuration.add(folder, toPackagePath(folderToPackageMapping.get(folder)));
-
-            // This is the 5.4 version; once 5.3 support is dropped, this can be simplified, and the
-            // "meta/" prefix stripped out.
-
-            String folderSuffix = folder.equals("") ? folder : "/" + folder;
-
-            configuration.add("meta" + folderSuffix, "META-INF/assets" + folderSuffix);
-        }
-    }
-
-    @Contribute(ClasspathAssetAliasManager.class)
-    public static void addApplicationAndTapestryMappings(MappedConfiguration<String, String> configuration,
-
-                                                         @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
-                                                         String appPackage)
-    {
-        configuration.add("tapestry", "org/apache/tapestry5");
-
-        configuration.add("app", toPackagePath(appPackage));
-    }
-
-    /**
-     * Contributes an handler for each mapped classpath alias, as well handlers for context assets
-     * and stack assets (combined {@link JavaScriptStack} files).
-     */
-    @Contribute(Dispatcher.class)
-    @AssetRequestDispatcher
-    public static void provideBuiltinAssetDispatchers(MappedConfiguration<String, AssetRequestHandler> configuration,
-
-                                                      @ContextProvider
-                                                      AssetFactory contextAssetFactory,
-
-                                                      @Autobuild
-                                                      StackAssetRequestHandler stackAssetRequestHandler,
-
-                                                      ClasspathAssetAliasManager classpathAssetAliasManager, ResourceStreamer streamer,
-                                                      AssetResourceLocator assetResourceLocator)
-    {
-        Map<String, String> mappings = classpathAssetAliasManager.getMappings();
-
-        for (String folder : mappings.keySet())
-        {
-            String path = mappings.get(folder);
-
-            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetResourceLocator, path));
-        }
-
-        configuration.add(RequestConstants.CONTEXT_FOLDER,
-                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
-
-        configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
-
-    }
-
-    private static String toPackagePath(String packageName)
-    {
-        return packageName.replace('.', '/');
-    }
 
     @Contribute(ComponentClassResolver.class)
     public static void setupCoreAndAppLibraries(Configuration<LibraryMapping> configuration,
@@ -1221,15 +1139,6 @@ public final class TapestryModule
         return service;
     }
 
-    @Marker(ContextProvider.class)
-    public AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
-
-                                                 AssetPathConstructor assetPathConstructor,
-
-                                                 AssetPathConverter converter)
-    {
-        return new ContextAssetFactory(assetPathConstructor, globals.getContext(), converter);
-    }
 
     /**
      * Builds the PropBindingFactory as a chain of command. The terminator of
@@ -1600,16 +1509,6 @@ public final class TapestryModule
         configuration.add("session", sessionStategy);
     }
 
-    public void contributeAssetSource(MappedConfiguration<String, AssetFactory> configuration, @ContextProvider
-    AssetFactory contextAssetFactory,
-
-                                      @ClasspathProvider
-                                      AssetFactory classpathAssetFactory)
-    {
-        configuration.add(AssetConstants.CONTEXT, contextAssetFactory);
-        configuration.add(AssetConstants.CLASSPATH, classpathAssetFactory);
-    }
-
     /**
      * Contributes handlers for the following types:
      * <dl>
@@ -2125,8 +2024,6 @@ public final class TapestryModule
 
         configuration.add(SymbolConstants.CLUSTERED_SESSIONS, true);
 
-        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
-
         configuration.add(SymbolConstants.COMPRESS_WHITESPACE, true);
 
         configuration.add(MetaDataConstants.SECURE_PAGE, false);
@@ -2586,29 +2483,6 @@ public final class TapestryModule
     }
 
     /**
-     * Contributes:
-     * <dl>
-     * <dt>ClientLocalization</dt>
-     * <dd>A virtual resource of formatting symbols for decimal numbers</dd>
-     * <dt>Core</dt>
-     * <dd>Built in messages used by Tapestry's default validators and components</dd>
-     * <dt>AppCatalog</dt>
-     * <dd>The Resource defined by {@link SymbolConstants#APPLICATION_CATALOG}</dd>
-     * <dt>
-     *
-     * @since 5.2.0
-     */
-    @Contribute(ComponentMessagesSource.class)
-    public static void setupGlobalMessageCatalog(AssetSource assetSource,
-                                                 @Symbol(SymbolConstants.APPLICATION_CATALOG)
-                                                 Resource applicationCatalog, OrderedConfiguration<Resource> configuration)
-    {
-        configuration.add("ClientLocalization", new ClientLocalizationMessageResource());
-        configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
-        configuration.add("AppCatalog", applicationCatalog);
-    }
-
-    /**
      * Contributes extractors for {@link Meta}, {@link Secure}, {@link ContentType} and {@link WhitelistAccessOnly} annotations.
      *
      * @since 5.2.0

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetChecksumGenerator.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetChecksumGenerator.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetChecksumGenerator.java
new file mode 100644
index 0000000..37588ec
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetChecksumGenerator.java
@@ -0,0 +1,37 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.services.assets;
+
+import org.apache.tapestry5.ioc.Resource;
+
+import java.io.IOException;
+
+/**
+ * Generates a checksum of an arbitrary {@link org.apache.tapestry5.Resource} which can be incorporated into
+ * the {@linkplain org.apache.tapestry5.Asset#toClientURL() client URL} of an Asset.
+ *
+ * @since 5.4
+ */
+public interface AssetChecksumGenerator
+{
+    /**
+     * Given a raw resource, generates an MD5 checksum of the resource's contents.
+     *
+     * @param resource
+     * @return checksum of contents
+     * @throws IOException
+     */
+    String generateChecksum(Resource resource) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/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 de9f317..1412a7b 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
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011 The Apache Software Foundation
+// Copyright 2010, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,10 +14,14 @@
 
 package org.apache.tapestry5.services.assets;
 
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
+
 /**
  * Encapsulates the logic or creating the path portion of an asset URL, including
  * the application version.
  *
+ * @see org.apache.tapestry5.services.PathConstructor
  * @since 5.2.0
  */
 public interface AssetPathConstructor
@@ -30,10 +34,11 @@ public interface AssetPathConstructor
      * @param path
      *         within the virtual folder (should <em>not</em> start with a slash). May be the empty string.
      *         When non-blank, separated from the rest of the path with a slash.
-     * @return path portion of asset URL, including the context path, the application folder,
-     *         the /assets/ virtual folder, the application
-     *         version number,
-     *         the virtual folder, and the path extension
+     * @param resource
+     *         underlying resource for the asset path, used to compute checksums (since 5.4)
+     * @return path portion of asset URL, which is everything needed by the {@link org.apache.tapestry5.internal.services.AssetDispatcher}
+     *         to find and stream the resource
      */
-    String constructAssetPath(String virtualFolder, String path);
+    @IncompatibleChange(release = "5.4", details = "resource parameter added")
+    String constructAssetPath(String virtualFolder, String path, Resource resource);
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
index a405ad9..c0acce3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetRequestHandler.java
@@ -1,4 +1,4 @@
-// Copyright 2010, 2012 The Apache Software Foundation
+// Copyright 2010, 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.
@@ -15,7 +15,6 @@
 package org.apache.tapestry5.services.assets;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.services.AssetDispatcher;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
@@ -25,20 +24,13 @@ import java.io.IOException;
 
 /**
  * Handler for asset requests, which expose some kind of {@link Asset} to
- * the user agent (i.e., the client web browser). Starting in Tapestry 5.2,
- * asset paths are more structured, consisting of four parts:
- * <ul>
- * <li><em>application folder</em> -- optional, based on {@link SymbolConstants#APPLICATION_FOLDER} Symbol     </li>
- * <li><code>assets</code> -- the root path for all assets (as defined by {@link SymbolConstants#ASSET_PATH_PREFIX} Symbol)
- * <li><em>application version</em> -- the application version, as defined by the
- * {@link SymbolConstants#APPLICATION_VERSION} symbol
- * <li><em>handler id</em> -- a handler for this part of the asset path (defined by contributions to the
- * <code>AssetDispatcher</code> service)
- * <li><em>extra path</em> -- additional path beyond the handler id, used to identify the specific resource
- * </ul>
+ * the user agent (i.e., the client web browser). When contributed to the {@link AssetDispatcher} service,
+ * the contributed key is a handler id (such as "meta/core").
  * <p/>
- * So, an example path might be <code>/assets/1.0.1/corelib/components/select.png</code>. The handler id would be
- * <code>corelib</code>, and the extra path would be <code>components/select.png</code>.
+ * An example request path might be <code>/assets/meta/core/dd8d73ac51dbab28caaec4865d302bf2/deselect.png</code>.
+ * The handler id would be
+ * <code>meta/core</code>, the {@linkplain AssetChecksumGenerator checksum of the resource content} is the
+ * hex string, and the extra path would be <code>select.png</code>.
  *
  * @see AssetDispatcher
  * @see org.apache.tapestry5.services.AssetRequestDispatcher
@@ -57,10 +49,6 @@ public interface AssetRequestHandler
      * The handler should return true if it provided a response. If the handler returns false, this indicates that the
      * extra path did not identify a known asset (virtual or otherwise) and the AssetDispatcher service should send a
      * {@link HttpServletResponse#SC_NOT_FOUND} response.
-     * <p/>
-     * AssetRequestHandlers are contributed to the {@link AssetDispatcher} service as a mapped configuration; the
-     * key is the path used to select the handler.  For a request with path "/assets/{appversion}/handler/extra/path" and a key of "handler"
-     * the extra path will be "extra/path".
      *
      * @param request
      *         incoming asset request

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index aea9cc4..14cd162 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -1,4 +1,4 @@
-// Copyright 2011, 2012 The Apache Software Foundation
+// Copyright 2011, 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.
@@ -15,15 +15,19 @@
 package org.apache.tapestry5.services.assets;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.internal.AssetConstants;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.services.*;
 import org.apache.tapestry5.internal.services.assets.*;
-import org.apache.tapestry5.ioc.MappedConfiguration;
-import org.apache.tapestry5.ioc.OperationTracker;
-import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.internal.services.messages.ClientLocalizationMessageResource;
+import org.apache.tapestry5.ioc.*;
 import org.apache.tapestry5.ioc.annotations.*;
 import org.apache.tapestry5.ioc.services.FactoryDefaults;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
-import org.apache.tapestry5.services.AssetSource;
-import org.apache.tapestry5.services.Core;
+import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.messages.ComponentMessagesSource;
+
+import java.util.Map;
 
 /**
  * @since 5.3
@@ -33,21 +37,44 @@ public class AssetsModule
 {
     public static void bind(ServiceBinder binder)
     {
+        binder.bind(AssetFactory.class, ClasspathAssetFactory.class).withSimpleId();
+        binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
+        binder.bind(AssetPathConstructor.class, AssetPathConstructorImpl.class);
+        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
+        binder.bind(AssetSource.class, AssetSourceImpl.class);
         binder.bind(StreamableResourceSource.class, StreamableResourceSourceImpl.class);
         binder.bind(CompressionAnalyzer.class, CompressionAnalyzerImpl.class);
         binder.bind(ContentTypeAnalyzer.class, ContentTypeAnalyzerImpl.class);
         binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
         binder.bind(ResourceMinimizer.class, MasterResourceMinimizer.class);
+        binder.bind(AssetChecksumGenerator.class, AssetChecksumGeneratorImpl.class);
+    }
+
+    @Contribute(AssetSource.class)
+    public void configureStandardAssetFactories(MappedConfiguration<String, AssetFactory> configuration,
+                                                @ContextProvider
+                                                AssetFactory contextAssetFactory,
+
+                                                @ClasspathProvider
+                                                AssetFactory classpathAssetFactory)
+    {
+        configuration.add(AssetConstants.CONTEXT, contextAssetFactory);
+        configuration.add(AssetConstants.CLASSPATH, classpathAssetFactory);
     }
 
+
     @Contribute(SymbolProvider.class)
     @FactoryDefaults
     public static void setupSymbols(MappedConfiguration<String, Object> configuration)
     {
+        // Minification may be enabled in production mode, but unless a minimizer is provided, nothing
+        // will change.
         configuration.add(SymbolConstants.MINIFICATION_ENABLED, SymbolConstants.PRODUCTION_MODE_VALUE);
         configuration.add(SymbolConstants.GZIP_COMPRESSION_ENABLED, true);
         configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
         configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
+
+        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
     }
 
     // The use of decorators is to allow third-parties to get their own extensions
@@ -156,4 +183,108 @@ public class AssetsModule
         configuration.add("image/png", false);
         configuration.add("application/x-shockwave-flash", false);
     }
+
+    @Marker(ContextProvider.class)
+    public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
+
+                                                        AssetPathConstructor assetPathConstructor,
+
+                                                        AssetPathConverter converter)
+    {
+        return new ContextAssetFactory(assetPathConstructor, globals.getContext(), converter);
+    }
+
+    @Contribute(ClasspathAssetAliasManager.class)
+    public static void addApplicationAndTapestryMappings(MappedConfiguration<String, String> configuration,
+
+                                                         @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
+                                                         String appPackage)
+    {
+        configuration.add("tapestry", "org/apache/tapestry5");
+
+        configuration.add("app", toPackagePath(appPackage));
+    }
+
+    /**
+     * Contributes an handler for each mapped classpath alias, as well handlers for context assets
+     * and stack assets (combined {@link org.apache.tapestry5.services.javascript.JavaScriptStack} files).
+     */
+    @Contribute(Dispatcher.class)
+    @AssetRequestDispatcher
+    public static void provideBuiltinAssetDispatchers(MappedConfiguration<String, AssetRequestHandler> configuration,
+
+                                                      @ContextProvider
+                                                      AssetFactory contextAssetFactory,
+
+                                                      @Autobuild
+                                                      StackAssetRequestHandler stackAssetRequestHandler,
+
+                                                      ClasspathAssetAliasManager classpathAssetAliasManager, ResourceStreamer streamer,
+                                                      AssetResourceLocator assetResourceLocator)
+    {
+        Map<String, String> mappings = classpathAssetAliasManager.getMappings();
+
+        for (String folder : mappings.keySet())
+        {
+            String path = mappings.get(folder);
+
+            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetResourceLocator, path));
+        }
+
+        configuration.add(RequestConstants.CONTEXT_FOLDER,
+                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
+
+        configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
+
+    }
+
+    @Contribute(ClasspathAssetAliasManager.class)
+    public static void addMappingsForLibraryVirtualFolders(MappedConfiguration<String, String> configuration,
+                                                           ComponentClassResolver resolver)
+    {
+        // Each library gets a mapping or its folder automatically
+
+        Map<String, String> folderToPackageMapping = resolver.getFolderToPackageMapping();
+
+        for (String folder : folderToPackageMapping.keySet())
+        {
+            // This is the 5.3 version, which is still supported:
+            configuration.add(folder, toPackagePath(folderToPackageMapping.get(folder)));
+
+            // This is the 5.4 version; once 5.3 support is dropped, this can be simplified, and the
+            // "meta/" prefix stripped out.
+
+            String folderSuffix = folder.equals("") ? folder : "/" + folder;
+
+            configuration.add("meta" + folderSuffix, "META-INF/assets" + folderSuffix);
+        }
+    }
+
+    private static String toPackagePath(String packageName)
+    {
+        return packageName.replace('.', '/');
+    }
+
+    /**
+     * Contributes:
+     * <dl>
+     * <dt>ClientLocalization</dt>
+     * <dd>A virtual resource of formatting symbols for decimal numbers</dd>
+     * <dt>Core</dt>
+     * <dd>Built in messages used by Tapestry's default validators and components</dd>
+     * <dt>AppCatalog</dt>
+     * <dd>The Resource defined by {@link SymbolConstants#APPLICATION_CATALOG}</dd>
+     * <dt>
+     *
+     * @since 5.2.0
+     */
+    @Contribute(ComponentMessagesSource.class)
+    public static void setupGlobalMessageCatalog(AssetSource assetSource,
+                                                 @Symbol(SymbolConstants.APPLICATION_CATALOG)
+                                                 Resource applicationCatalog, OrderedConfiguration<Resource> configuration)
+    {
+        configuration.add("ClientLocalization", new ClientLocalizationMessageResource());
+        configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
+        configuration.add("AppCatalog", applicationCatalog);
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java b/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
index 041e812..786b25b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2006-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.
@@ -644,16 +644,14 @@ public abstract class TapestryTestCase extends IOCTestCase
         response.setDateHeader(headerName, date);
     }
 
+    /**
+     * @deprecated Deprecated in 5.4 with no replacement.
+     */
     protected final void train_toClientURL(Asset asset, String URL)
     {
         expect(asset.toClientURL()).andReturn(URL).atLeastOnce();
     }
 
-    protected final void train_toClientURL(ClasspathAssetAliasManager manager, String resourcePath, String clientURL)
-    {
-        expect(manager.toClientURL(resourcePath)).andReturn(clientURL);
-    }
-
     protected final void train_toRedirectURI(Link link, String URI)
     {
         expect(link.toRedirectURI()).andReturn(URI).atLeastOnce();

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/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 f639522..7e41fbf 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImplTest.groovy
@@ -1,9 +1,12 @@
 package org.apache.tapestry5.internal.services.assets
 
+import org.apache.tapestry5.ioc.Resource
 import org.apache.tapestry5.ioc.test.TestBase
+import org.apache.tapestry5.services.BaseURLSource
+import org.apache.tapestry5.services.PathConstructor
 import org.apache.tapestry5.services.Request
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator
 import org.testng.annotations.Test
-import org.apache.tapestry5.services.BaseURLSource
 
 class AssetPathConstructorImplTest extends TestBase {
 
@@ -12,69 +15,52 @@ class AssetPathConstructorImplTest extends TestBase {
 
     def request = newMock(Request)
 
-    def apc = new AssetPathConstructorImpl(request, null, "", "123", "", false, "assets")
-
-    replay()
+    def baseURLSource = newMock(BaseURLSource)
 
-    assert apc.constructAssetPath("virt", "extra") == "/assets/123/virt/extra"
+    def pc = newMock(PathConstructor)
 
-    assert apc.constructAssetPath("virtb", "") == "/assets/123/virtb"
+    def gen = newMock(AssetChecksumGenerator)
 
-    verify()
-  }
+    def virtExtra = newMock(Resource)
 
-@Test
-  void "construct an asset path with no extra path"() {
+    def virtb = newMock(Resource)
 
-    def request = newMock(Request)
+    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
 
-    def apc = new AssetPathConstructorImpl(request, null, "", "123", "", false, "assets")
+    expect(gen.generateChecksum(virtExtra)).andReturn("abc")
 
     replay()
 
-    assert apc.constructAssetPath("virtb", "") == "/assets/123/virtb"
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, false, "assets", pc, gen)
 
-    verify()
-  }
-  @Test
-  void "construct asset path with an application folder"() {
-    def request = newMock(Request)
-
-    def apc = new AssetPathConstructorImpl(request, null, "", "123", "myapp", false, "assets")
-
-    replay()
-
-    assert apc.constructAssetPath("virt", "extra") == "/myapp/assets/123/virt/extra"
+    assert apc.constructAssetPath("virt", "extra.png", virtExtra) == "/assets/virt/abc/extra.png"
 
     verify()
   }
 
   @Test
-  void "construct asset path with a context path"() {
+  void "fully qualified path has base URL prepended"() {
     def request = newMock(Request)
+    def baseURLSource = newMock(BaseURLSource)
 
-    def apc = new AssetPathConstructorImpl(request, null, "/ctx", "123", "myapp", false, "assets")
-
-    replay()
-
-    assert apc.constructAssetPath("virt", "extra") == "/ctx/myapp/assets/123/virt/extra"
+    def pc = newMock(PathConstructor)
 
-    verify()
-  }
+    def gen = newMock(AssetChecksumGenerator)
 
-  @Test
-  void "fully qualified path has base URL prepended"() {
-    def request = newMock(Request)
-    def baseURLSource = newMock(BaseURLSource)
+    def r = newMock(Resource)
 
-    def apc = new AssetPathConstructorImpl(request, baseURLSource, "/mycontext", "123", "myapp", true, "assets")
+    expect(pc.constructClientPath("assets", "")).andReturn("/assets/")
 
     expect(request.secure).andReturn(false)
     expect(baseURLSource.getBaseURL(false)).andReturn("http://localhost:8080")
 
+    expect(gen.generateChecksum(r)).andReturn("911")
+
     replay()
 
-    assert apc.constructAssetPath("virt", "extra") == "http://localhost:8080/mycontext/myapp/assets/123/virt/extra"
+    def apc = new AssetPathConstructorImpl(request, baseURLSource, true, "assets", pc, gen)
+
+    assert apc.constructAssetPath("virt", "icon.gif", r) == "http://localhost:8080/assets/virt/911/icon.gif"
 
     verify()
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
index 886a156..776ad50 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2009, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2006, 2007, 2009, 2010, 2011, 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.
@@ -16,14 +16,19 @@ package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.services.BaseURLSource;
 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
+import org.apache.tapestry5.services.PathConstructor;
 import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import java.io.IOException;
 import java.util.Map;
 
 public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
@@ -76,20 +81,28 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     }
 
     @Test(dataProvider = "to_client_url_data")
-    public void to_client_url(String resourcePath, String expectedClientURL)
+    public void to_client_url(String resourcePath, String expectedClientURL) throws IOException
     {
         Request request = mockRequest();
+        Resource r = mockResource();
+
+        expect(r.getPath()).andReturn(resourcePath);
 
         BaseURLSource baseURLSource = newMock(BaseURLSource.class);
+        PathConstructor pc = newMock(PathConstructor.class);
+        AssetChecksumGenerator acg = newMock(AssetChecksumGenerator.class);
+
+        expect(pc.constructClientPath("assets", "")).andReturn("/ctx/assets/");
+        expect(acg.generateChecksum(r)).andReturn("abcde");
 
         replay();
 
         ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(
                 new AssetPathConstructorImpl(request,
-                baseURLSource, "/ctx", APP_VERSION, "", false, "assets"), configuration());
+                        baseURLSource, false, "assets", pc, acg), configuration());
 
-        String expectedPath = "/ctx/assets/" + APP_VERSION + "/" + expectedClientURL;
-        assertEquals(manager.toClientURL(resourcePath), expectedPath);
+        String expectedPath = "/ctx/assets/" + expectedClientURL.replace("#", "abcde");
+        assertEquals(manager.toClientURL(r), expectedPath);
 
         verify();
     }
@@ -97,11 +110,17 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     @Test
     public void failure_if_path_not_in_mapped_alias_folder()
     {
-        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(null, configuration());
+        AssetPathConstructor pc = newMock(AssetPathConstructor.class);
+        ClasspathAssetAliasManager manager = new ClasspathAssetAliasManagerImpl(pc, configuration());
+        Resource resource = mockResource();
+
+        expect(resource.getPath()).andReturn("org/example/icons/flag.gif").atLeastOnce();
+
+        replay();
 
         try
         {
-            manager.toClientURL("org/example/icons/flag.gif");
+            manager.toClientURL(resource);
             unreachable();
         } catch (UnknownValueException ex)
         {
@@ -110,6 +129,8 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
             assertListsEquals(ex.getAvailableValues().getValues(), "com/example/mylib", "org/apache/tapestry5",
                     "org/apache/tapestry5/internal");
         }
+
+        verify();
     }
 
     @DataProvider
@@ -117,10 +138,10 @@ public class ClasspathAssetAliasManagerImplTest extends InternalBaseTestCase
     {
         return new Object[][]
                 {
-                        {"com/example/mylib/Foo.bar", "mylib/Foo.bar"},
-                        {"com/example/mylib/nested/Foo.bar", "mylib/nested/Foo.bar"},
-                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal/Foo.bar"},
-                        {"org/apache/tapestry5/Foo.bar", "tapestry/Foo.bar"},};
+                        {"com/example/mylib/Foo.bar", "mylib/#/Foo.bar"},
+                        {"com/example/mylib/nested/Foo.bar", "mylib/#/nested/Foo.bar"},
+                        {"org/apache/tapestry5/internal/Foo.bar", "tapestry-internal/#/Foo.bar"},
+                        {"org/apache/tapestry5/Foo.bar", "tapestry/#/Foo.bar"},};
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
index 2ed2160..4a393a2 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetFactoryTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2011 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ public class ClasspathAssetFactoryTest extends InternalBaseTestCase
 
         String expectedClientURL = "/context/asset/foo/Bar.txt";
 
-        train_toClientURL(aliasManager, "foo/Bar.txt", expectedClientURL);
+        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
 
         replay();
 
@@ -68,7 +68,7 @@ public class ClasspathAssetFactoryTest extends InternalBaseTestCase
 
         String expectedClientURL = "/context/asset/foo/Bar.txt";
 
-        train_toClientURL(aliasManager, "foo/Bar.txt", expectedClientURL);
+        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
 
         replay();
 
@@ -97,7 +97,7 @@ public class ClasspathAssetFactoryTest extends InternalBaseTestCase
 
         String expectedClientURL = "/context/asset/foo/Bar.ABC123.txt";
 
-        train_toClientURL(aliasManager, "foo/Bar.ABC123.txt", expectedClientURL);
+        expect(aliasManager.toClientURL(r)).andReturn(expectedClientURL);
 
         replay();
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
index ec22315..2f19fe0 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2009, 2012 The Apache Software Foundation
+// Copyright 2006, 2007, 2009, 2012, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,13 +15,11 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.services.AssetFactory;
-import org.apache.tapestry5.services.BaseURLSource;
 import org.apache.tapestry5.services.Context;
-import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.testng.annotations.Test;
 
 public class ContextAssetFactoryTest extends InternalBaseTestCase
@@ -32,11 +30,11 @@ public class ContextAssetFactoryTest extends InternalBaseTestCase
     public void root_resource()
     {
         Context context = mockContext();
-        // Request request = mockRequest();
+        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
 
         replay();
 
-        AssetFactory factory = new ContextAssetFactory(null, context, converter);
+        AssetFactory factory = new ContextAssetFactory(apc, context, converter);
 
         assertEquals(factory.getRootResource().toString(), "context:/");
 
@@ -47,104 +45,27 @@ public class ContextAssetFactoryTest extends InternalBaseTestCase
     public void asset_client_URL()
     {
         Context context = mockContext();
-        Request request = mockRequest();
-
-        BaseURLSource baseURLSource = newMock(BaseURLSource.class);
-
-        Resource r = new ContextResource(context, "foo/Bar.txt");
-
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(
-                new AssetPathConstructorImpl(request,
-                        baseURLSource,
-                        "/context", "4.5.6",
-                        "",
-                        false,
-                        "assets"
-                ),
-                context,
-                new IdentityAssetPathConverter()
-        );
-
-        Asset asset = factory.createAsset(r);
-
-        assertSame(asset.getResource(), r);
-        assertEquals(asset.toClientURL(), "/context/assets/4.5.6/ctx/foo/Bar.txt");
-
-        // In real life, toString() is the same as toClientURL(), but we're testing
-        // that the optimize method is getting called, basically.
-
-        assertEquals(asset.toString(), "/context/assets/4.5.6/ctx/foo/Bar.txt");
-
-        verify();
-    }
-
-    @Test
-    public void asset_client_URL_with_default_context() {
-        Context context = mockContext();
-        Request request = mockRequest();
-
-        BaseURLSource baseURLSource = newMock(BaseURLSource.class);
+        AssetPathConstructor apc = newMock(AssetPathConstructor.class);
 
         Resource r = new ContextResource(context, "foo/Bar.txt");
 
-        replay();
-
-        AssetFactory factory = new ContextAssetFactory(
-                new AssetPathConstructorImpl(request,
-                        baseURLSource,
-                        "", "4.5.6",
-                        "",
-                        false,
-                        "assets"
-                ),
-                context,
-                new IdentityAssetPathConverter()
-        );
-
-        Asset asset = factory.createAsset(r);
+        String expectedURL = "/expected-url";
 
-        assertEquals(asset.toClientURL(), "/assets/4.5.6/ctx/foo/Bar.txt");
-
-        verify();
-    }
-
-    @Test
-    public void asset_client_URL_fully_qualified()
-    {
-        Context context = mockContext();
-        Request request = mockRequest();
-
-        BaseURLSource baseURLSource = newMock(BaseURLSource.class);
-
-        Resource r = new ContextResource(context, "foo/Bar.txt");
-
-        train_getBaseSource(baseURLSource, request);
+        expect(apc.constructAssetPath("ctx", "foo/Bar.txt", r)).andReturn(expectedURL).atLeastOnce();
 
         replay();
 
-        AssetFactory factory = new ContextAssetFactory(
-                new AssetPathConstructorImpl(request,
-                        baseURLSource,
-                        "/context", "4.5.6",
-                        "",
-                        true,
-                        "assets"
-                ),
-                context,
-                new IdentityAssetPathConverter()
-        );
+        AssetFactory factory = new ContextAssetFactory(apc, context, new IdentityAssetPathConverter());
 
         Asset asset = factory.createAsset(r);
 
         assertSame(asset.getResource(), r);
-        assertEquals(asset.toClientURL(), "/context/assets/4.5.6/ctx/foo/Bar.txt");
+
+        assertSame(asset.toClientURL(), expectedURL);
 
         // In real life, toString() is the same as toClientURL(), but we're testing
         // that the optimize method is getting called, basically.
-
-        assertEquals(asset.toString(), "/context/assets/4.5.6/ctx/foo/Bar.txt");
+        assertSame(asset.toString(), expectedURL);
 
         verify();
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/8fbbd594/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
index 1629e95..e343b2b 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/assets/ContextAssetRequestHandlerTest.java
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2013 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ public class ContextAssetRequestHandlerTest extends TestBase
     {
         ContextAssetRequestHandler handler = new ContextAssetRequestHandler(null, null);
 
-        assertFalse(handler.handleAssetRequest(null, null, path), "Handler should return false for invalid path.");
+        assertFalse(handler.handleAssetRequest(null, null, "fake-checksum/" + path),
+            "Handler should return false for invalid path.");
     }
 }