You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2011/03/01 20:29:10 UTC

svn commit: r1075990 [1/2] - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/internal/services/assets/ main/java/org/apache/tapestry5/services/ main/java/org/apache/tapest...

Author: hlship
Date: Tue Mar  1 19:29:08 2011
New Revision: 1075990

URL: http://svn.apache.org/viewvc?rev=1075990&view=rev
Log:
TAP5-73: Replace the old implementation of ResourceStreamer with a new one, organized around the new StreamableResourceSource service

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressionAnalyzerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContentTypeAnalyzerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTracker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCachingInterceptor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressedCachingInterceptor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionAnalyzer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionStatus.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ContentTypeAnalyzer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceFeature.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceSource.java
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ResourceStreamerImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImplTest.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCache.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCacheImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ResourceCacheImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java Tue Mar  1 19:29:08 2011
@@ -38,7 +38,17 @@ import org.apache.tapestry5.ioc.services
 import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
 import org.apache.tapestry5.ioc.services.PerthreadManager;
 import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
-import org.apache.tapestry5.services.*;
+import org.apache.tapestry5.services.ComponentClasses;
+import org.apache.tapestry5.services.ComponentLayer;
+import org.apache.tapestry5.services.ComponentMessages;
+import org.apache.tapestry5.services.ComponentTemplates;
+import org.apache.tapestry5.services.Core;
+import org.apache.tapestry5.services.InvalidationEventHub;
+import org.apache.tapestry5.services.LinkCreationListener2;
+import org.apache.tapestry5.services.LocalizationSetter;
+import org.apache.tapestry5.services.RequestGlobals;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.UpdateListenerHub;
 import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
 import org.slf4j.Logger;
 
@@ -91,6 +101,7 @@ public class InternalModule
         binder.bind(AssetResourceLocator.class);
         binder.bind(JavaScriptStackPathConstructor.class);
         binder.bind(AjaxFormUpdateController.class);
+        binder.bind(ResourceCache.class, ResourceCacheImpl.class);
     }
 
     /**
@@ -263,14 +274,6 @@ public class InternalModule
         };
     }
 
-    public ResourceCache buildResourceCache(@Autobuild
-    ResourceCacheImpl service)
-    {
-        updateListenerHub.addUpdateListener(service);
-
-        return service;
-    }
-
     public ComponentTemplateSource buildComponentTemplateSource(TemplateParser parser, @Primary
     ComponentTemplateLocator locator, ClasspathURLConverter classpathURLConverter)
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCache.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCache.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCache.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCache.java Tue Mar  1 19:29:08 2011
@@ -1,10 +1,10 @@
-// Copyright 2006, 2008, 2009 The Apache Software Foundation
+// Copyright 2006, 2008, 2009, 2011 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
+// 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,
@@ -28,7 +28,7 @@ public interface ResourceCache extends I
     /**
      * Returns true if the path requires that the client URL for the resource include a digest to validate that the
      * client is authorized to access the resource.
-     *
+     * 
      * @param resource
      * @return true if digest is required for the resource
      * @see ResourceDigestGenerator#requiresDigest(String)
@@ -36,17 +36,8 @@ public interface ResourceCache extends I
     boolean requiresDigest(Resource resource);
 
     /**
-     * Returns the contents of the resource
-     *
-     * @param resource
-     * @return access to compressed and uncompressed streams
-     * @since 5.1.0.0
-     */
-    StreamableResource getStreamableResource(Resource resource);
-
-    /**
      * Returns the digest for the given path.
-     *
+     * 
      * @param resource
      * @return the digest, or null if the resource does not exist
      */
@@ -54,7 +45,7 @@ public interface ResourceCache extends I
 
     /**
      * Returns the time modified for the resource.
-     *
+     * 
      * @param resource
      * @return the date time modified for the path, or a negative value if the resource does not exist
      */

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCacheImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCacheImpl.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCacheImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceCacheImpl.java Tue Mar  1 19:29:08 2011
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011 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
+// 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,
@@ -14,24 +14,22 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
-import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
+import org.apache.tapestry5.services.InvalidationListener;
 import org.apache.tapestry5.services.ResourceDigestGenerator;
-import org.apache.tapestry5.services.UpdateListener;
 
-import java.net.URL;
-import java.util.Map;
-
-public class ResourceCacheImpl extends InvalidationEventHubImpl implements ResourceCache,
-        UpdateListener
+public class ResourceCacheImpl implements ResourceCache, InvalidationListener
 {
-    private final URLChangeTracker tracker;
-
     private final ResourceDigestGenerator digestGenerator;
 
+    private final ResourceChangeTracker resourceChangeTracker;
+
     private final Map<Resource, Cached> cache = CollectionFactory.newConcurrentMap();
 
     final static long MISSING_RESOURCE_TIME_MODIFIED = -1L;
@@ -44,44 +42,28 @@ public class ResourceCacheImpl extends I
 
         final long timeModified;
 
-        final StreamableResource streamable;
-
         Cached(Resource resource)
         {
             requiresDigest = digestGenerator.requiresDigest(resource.getPath());
 
             URL url = resource.toURL();
 
-            // The url may be null when a request for a protected asset arrives, because the
-            // Resource initially is for the file with the digest incorporated into the path, which
-            // means
-            // no underlying file exists. Subsequently, we'll strip out the digest and resolve
-            // to an actual resource.
+            digest = (requiresDigest && url != null) ? digestGenerator.generateDigest(url) : null;
 
-            digest = (requiresDigest && url != null) ? digestGenerator.generateDigest(url)
-                                                     : null;
-
-            timeModified = url != null ? tracker.add(url) : MISSING_RESOURCE_TIME_MODIFIED;
-
-            streamable = url == null ? null : new StreamableResourceImpl(url, timeModified);
+            timeModified = url != null ? resourceChangeTracker.trackResource(resource) : MISSING_RESOURCE_TIME_MODIFIED;
         }
     }
 
-    public ResourceCacheImpl(final ResourceDigestGenerator digestGenerator, ClasspathURLConverter classpathURLConverter)
+    public ResourceCacheImpl(ResourceDigestGenerator digestGenerator, ResourceChangeTracker resourceChangeTracker)
     {
         this.digestGenerator = digestGenerator;
-        tracker = new URLChangeTracker(classpathURLConverter,true);
+        this.resourceChangeTracker = resourceChangeTracker;
     }
 
-    public void checkForUpdates()
+    @PostInjection
+    public void listenForInvalidations()
     {
-        if (tracker.containsChanges())
-        {
-            cache.clear();
-            tracker.clear();
-
-            fireInvalidationEvent();
-        }
+        resourceChangeTracker.addInvalidationListener(this);
     }
 
     private Cached get(Resource resource)
@@ -112,8 +94,14 @@ public class ResourceCacheImpl extends I
         return get(resource).requiresDigest;
     }
 
-    public StreamableResource getStreamableResource(Resource resource)
+    public void addInvalidationListener(InvalidationListener listener)
     {
-        return get(resource).streamable;
+        resourceChangeTracker.addInvalidationListener(listener);
     }
+
+    public void objectWasInvalidated()
+    {
+        cache.clear();
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamer.java Tue Mar  1 19:29:08 2011
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2011 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,38 +14,27 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.ioc.Resource;
-import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
-
 import java.io.IOException;
 
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
 /**
  * Responsible for streaming the contents of a resource to the client. The {@link org.apache.tapestry5.ioc.Resource} to
- * stream is almost always a {@link org.apache.tapestry5.ioc.internal.util.ClasspathResource}.
- * <p/>
- * The service's configuration is used to map file extensions to content types. Note: this only works for simple
- * extensions (i.e., "jpg") not for complex extensions (i.e., "tar.gz").
+ * stream is a {@link org.apache.tapestry5.ioc.internal.util.ClasspathResource} or {@link ContextResource}.
  * 
  * @since 5.1.0.0
  */
-@UsesMappedConfiguration(String.class)
 public interface ResourceStreamer
 {
     /**
      * Streams the content of the resource to the client (or sends
-     * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}).
-     */
-    void streamResource(Resource resource) throws IOException;
-
-    /**
-     * Analyzes the resource to determine what its content type is, possibly using the service's configuration.
+     * an alternative response such as {@link HttpServletResponse#SC_NOT_MODIFIED}). Encapsulates logic for compression
+     * and for caching.
      * 
-     * @param resource
-     *            to analyze
-     * @return content type
-     * @throws IOException
+     * @see StreamableResourceSource
      */
-    String getContentType(Resource resource) throws IOException;
+    void streamResource(Resource resource) throws IOException;
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java Tue Mar  1 19:29:08 2011
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010, 2011 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,23 +14,22 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
 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.Context;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletResponse;
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.StreamableResourceFeature;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 
 public class ResourceStreamerImpl implements ResourceStreamer
 {
@@ -42,41 +41,31 @@ public class ResourceStreamerImpl implem
 
     private final Response response;
 
-    private final Context context;
+    private final StreamableResourceSource streamableResourceSource;
 
     private final ResponseCompressionAnalyzer analyzer;
 
-    private final Map<String, String> configuration;
-
-    private final int compressionCutoff;
-
     private final boolean productionMode;
 
     public ResourceStreamerImpl(Request request,
 
     Response response,
 
-    Context context,
+    StreamableResourceSource streamableResourceSource,
 
     ResourceCache resourceCache,
 
-    Map<String, String> configuration,
-
     ResponseCompressionAnalyzer analyzer,
 
-    @Symbol(SymbolConstants.MIN_GZIP_SIZE)
-    int compressionCutoff,
-
     @Symbol(SymbolConstants.PRODUCTION_MODE)
     boolean productionMode)
     {
         this.request = request;
         this.response = response;
-        this.context = context;
+        this.streamableResourceSource = streamableResourceSource;
+
         this.resourceCache = resourceCache;
-        this.configuration = configuration;
         this.analyzer = analyzer;
-        this.compressionCutoff = compressionCutoff;
         this.productionMode = productionMode;
     }
 
@@ -90,6 +79,8 @@ public class ResourceStreamerImpl implem
 
         long ifModifiedSince = 0;
 
+        long modified = resourceCache.getTimeModified(resource);
+
         try
         {
             ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE_HEADER);
@@ -103,8 +94,6 @@ public class ResourceStreamerImpl implem
 
         if (ifModifiedSince > 0)
         {
-            long modified = resourceCache.getTimeModified(resource);
-
             if (ifModifiedSince >= modified)
             {
                 response.sendError(HttpServletResponse.SC_NOT_MODIFIED, "");
@@ -112,83 +101,35 @@ public class ResourceStreamerImpl implem
             }
         }
 
+        Set<StreamableResourceFeature> features = analyzer.isGZipSupported() ? StreamableResourceFeature.ALL
+                : StreamableResourceFeature.NO_COMPRESSION;
+
+        org.apache.tapestry5.services.assets.StreamableResource streamable = streamableResourceSource
+                .getStreamableResource(resource, features);
+
         // Prevent the upstream code from compressing when we don't want to.
 
         response.disableCompression();
-        
-        StreamableResource streamble = resourceCache.getStreamableResource(resource);
 
-        long lastModified = streamble.getLastModified();
+        // TODO: This may be broken, as we want the lastModified with only 1 second precision, which is
+        // as much as can be expressed via the HTTP header.
+
+        long lastModified = modified;
 
         response.setDateHeader("Last-Modified", lastModified);
 
         if (productionMode)
             response.setDateHeader("Expires", lastModified + InternalConstants.TEN_YEARS);
 
-        String contentType = identifyContentType(resource, streamble);
-
-        boolean compress = analyzer.isGZipSupported() && streamble.getSize(false) >= compressionCutoff
-                && analyzer.isCompressable(contentType);
+        response.setContentLength(streamable.getSize());
 
-        int contentLength = streamble.getSize(compress);
-
-        if (contentLength >= 0)
-            response.setContentLength(contentLength);
-
-        if (compress)
+        if (streamable.getCompression() == CompressionStatus.COMPRESSED)
             response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
 
-        InputStream is = null;
+        OutputStream os = response.getOutputStream(streamable.getContentType());
 
-        try
-        {
-            is = streamble.getStream(compress);
-
-            OutputStream os = response.getOutputStream(contentType);
-
-            TapestryInternalUtils.copy(is, os);
-
-            is.close();
-            is = null;
-
-            os.close();
-        }
-        finally
-        {
-            InternalUtils.close(is);
-        }
-    }
-
-    public String getContentType(Resource resource) throws IOException
-    {
-        return identifyContentType(resource, resourceCache.getStreamableResource(resource));
-    }
-
-    private String identifyContentType(Resource resource, StreamableResource streamble) throws IOException
-    {
-        String contentType = streamble.getContentType();
-
-        if ("content/unknown".equals(contentType))
-            contentType = null;
-
-        if (contentType != null)
-            return contentType;
-
-        contentType = context.getMimeType(resource.getPath());
-
-        if (contentType != null)
-            return contentType;
-
-        String file = resource.getFile();
-        int dotx = file.lastIndexOf('.');
-
-        if (dotx > 0)
-        {
-            String extension = file.substring(dotx + 1);
-
-            contentType = configuration.get(extension);
-        }
+        streamable.streamTo(os);
 
-        return contentType != null ? contentType : "application/octet-stream";
+        os.close();
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResponseCompressionAnalyzerImpl.java Tue Mar  1 19:29:08 2011
@@ -1,10 +1,10 @@
-// Copyright 2009 The Apache Software Foundation
+// Copyright 2009, 2010 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
+// 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,
@@ -14,42 +14,42 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.util.Collection;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.annotations.Symbol;
-import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
-import org.apache.tapestry5.SymbolConstants;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.Collection;
-import java.util.Map;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 public class ResponseCompressionAnalyzerImpl implements ResponseCompressionAnalyzer
 {
     private final HttpServletRequest request;
 
-    private final Map<String, Boolean> notCompressable = CollectionFactory.newCaseInsensitiveMap();
-
     private final boolean gzipCompressionEnabled;
 
-    public ResponseCompressionAnalyzerImpl(HttpServletRequest request, Collection<String> configuration,
-                                           @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
-                                           boolean gzipCompressionEnabled)
+    private final CompressionAnalyzer analyzer;
+
+    public ResponseCompressionAnalyzerImpl(HttpServletRequest request, CompressionAnalyzer analyzer, @Deprecated
+    Collection<String> configuration, @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
+    boolean gzipCompressionEnabled)
     {
         this.request = request;
+        this.analyzer = analyzer;
         this.gzipCompressionEnabled = gzipCompressionEnabled;
-
-        for (String contentType : configuration)
-            notCompressable.put(contentType, true);
     }
 
     public boolean isGZipSupported()
     {
-        if (!gzipCompressionEnabled) return false;
+        if (!gzipCompressionEnabled)
+            return false;
 
         String supportedEncodings = request.getHeader("Accept-Encoding");
 
-        if (supportedEncodings == null) return false;
+        if (supportedEncodings == null)
+            return false;
 
         for (String encoding : TapestryInternalUtils.splitAtCommas(supportedEncodings))
         {
@@ -62,10 +62,6 @@ public class ResponseCompressionAnalyzer
 
     public boolean isCompressable(String contentType)
     {
-        int x = contentType.indexOf(';');
-
-        String key = x < 0 ? contentType : contentType.substring(0, x);
-
-        return notCompressable.get(key) == null;
+        return analyzer.isCompressable(contentType);
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressionAnalyzerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressionAnalyzerImpl.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressionAnalyzerImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/CompressionAnalyzerImpl.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,42 @@
+// Copyright 2011 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 java.util.Map;
+
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
+
+public class CompressionAnalyzerImpl implements CompressionAnalyzer
+{
+    private final Map<String, Boolean> configuration;
+
+    public CompressionAnalyzerImpl(Map<String, Boolean> configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    public boolean isCompressable(String contentType)
+    {
+        assert contentType != null;
+
+        int x = contentType.indexOf(';');
+
+        String key = x < 0 ? contentType : contentType.substring(0, x);
+
+        Boolean result = configuration.get(key);
+
+        return result == null ? true : result.booleanValue();
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContentTypeAnalyzerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContentTypeAnalyzerImpl.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContentTypeAnalyzerImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ContentTypeAnalyzerImpl.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,53 @@
+// Copyright 2011 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 java.util.Map;
+
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.Context;
+import org.apache.tapestry5.services.assets.ContentTypeAnalyzer;
+
+public class ContentTypeAnalyzerImpl implements ContentTypeAnalyzer
+{
+    private final Context context;
+
+    private final Map<String, String> configuration;
+
+    public ContentTypeAnalyzerImpl(Context context, Map<String, String> configuration)
+    {
+        this.context = context;
+        this.configuration = configuration;
+    }
+
+    public String getContentType(Resource resource)
+    {
+        String extension = TapestryInternalUtils.toFileSuffix(resource.getFile());
+
+        String contentType = configuration.get(extension);
+
+        if (contentType != null)
+            return contentType;
+
+        contentType = context.getMimeType(resource.getFile());
+
+        if (contentType != null)
+            return contentType;
+
+        return "application/octet-stream";
+    }
+
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTracker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTracker.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTracker.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTracker.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,43 @@
+// Copyright 2011 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.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
+import org.apache.tapestry5.services.InvalidationEventHub;
+import org.apache.tapestry5.services.InvalidationListener;
+import org.apache.tapestry5.services.UpdateListener;
+
+/**
+ * Tracks resources (at least, resources that can change because they are on the file system) and
+ * acts as an {@link UpdateListener} to check for changes and notify its listeners.
+ * 
+ * @since 5.3.0
+ */
+public interface ResourceChangeTracker extends InvalidationEventHub
+{
+    /**
+     * Start tracking the resource (or return the last modified time of an already tracked resource). Only file system
+     * resources are tracked. Resources are tracked until <em>any</em> resource changes, at which points
+     * {@linkplain InvalidationListener listeners} are notified and the internal state
+     * is cleared.
+     * 
+     * @see URLChangeTracker
+     * @param resource
+     *            to track
+     * @return last modified time, to nearest second
+     */
+    long trackResource(Resource resource);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,58 @@
+// Copyright 2011 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.event.InvalidationEventHubImpl;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
+import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
+import org.apache.tapestry5.services.UpdateListener;
+import org.apache.tapestry5.services.UpdateListenerHub;
+
+public class ResourceChangeTrackerImpl extends InvalidationEventHubImpl implements ResourceChangeTracker,
+        UpdateListener
+{
+    private final URLChangeTracker tracker;
+
+    public ResourceChangeTrackerImpl(ClasspathURLConverter classpathURLConverter)
+    {
+        // Use granularity of seconds (not milliseconds) since that works properly
+        // with response headers for identifying last modified. Don't track
+        // folder changes, just changes to actual files.
+        tracker = new URLChangeTracker(classpathURLConverter, true, false);
+    }
+
+    @PostInjection
+    public void registerWithUpdateListenerHub(UpdateListenerHub hub)
+    {
+        hub.addUpdateListener(this);
+    }
+
+    public long trackResource(Resource resource)
+    {
+        return tracker.add(resource.toURL());
+    }
+
+    public void checkForUpdates()
+    {
+        if (tracker.containsChanges())
+        {
+            fireInvalidationEvent();
+            tracker.clear();
+        }
+    }
+
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCachingInterceptor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCachingInterceptor.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCachingInterceptor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCachingInterceptor.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,91 @@
+// Copyright 2011 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 java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.services.InvalidationListener;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceFeature;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+/**
+ * An interceptor for the {@link StreamableResourceSource} service that handles caching of content.
+ */
+public class SRSCachingInterceptor implements StreamableResourceSource, InvalidationListener
+{
+    private final ResourceChangeTracker tracker;
+
+    private final StreamableResourceSource delegate;
+
+    private final Map<Resource, StreamableResource> cache = CollectionFactory.newConcurrentMap();
+
+    public SRSCachingInterceptor(ResourceChangeTracker tracker, StreamableResourceSource delegate)
+    {
+        this.tracker = tracker;
+        this.delegate = delegate;
+    }
+
+    // See Brian's thread safety book for why it's better for this logic to be outside the constructor
+    @PostInjection
+    public void registerAsInvalidationListener()
+    {
+        tracker.addInvalidationListener(this);
+    }
+
+    public StreamableResource getStreamableResource(Resource baseResource, Set<StreamableResourceFeature> features)
+            throws IOException
+    {
+        if (!features.contains(StreamableResourceFeature.CACHING))
+            return delegate.getStreamableResource(baseResource, features);
+
+        StreamableResource result = cache.get(baseResource);
+
+        if (result == null)
+        {
+            result = delegate.getStreamableResource(baseResource, features);
+
+            if (isCacheable(result))
+            {
+                tracker.trackResource(baseResource);
+
+                cache.put(baseResource, result);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Always returns true; a subclass may extend this to only cache the resource in some circumstances.
+     * 
+     * @param resource
+     * @return true to cache the resource
+     */
+    protected boolean isCacheable(StreamableResource resource)
+    {
+        return true;
+    }
+
+    public void objectWasInvalidated()
+    {
+        cache.clear();
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressedCachingInterceptor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressedCachingInterceptor.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressedCachingInterceptor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressedCachingInterceptor.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,41 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+/**
+ * Specialization of {@link SRSCachingInterceptor} that only attempts to cache
+ * compressed resources.
+ */
+public class SRSCompressedCachingInterceptor extends SRSCachingInterceptor
+{
+    public SRSCompressedCachingInterceptor(ResourceChangeTracker tracker, StreamableResourceSource delegate)
+    {
+        super(tracker, delegate);
+    }
+
+    /**
+     * Return true only if the resource is compressed.
+     */
+    @Override
+    protected boolean isCacheable(StreamableResource resource)
+    {
+        return resource.getCompression() == CompressionStatus.COMPRESSED;
+    }
+
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/SRSCompressingInterceptor.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,75 @@
+// Copyright 2011 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 java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Set;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceFeature;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+public class SRSCompressingInterceptor implements StreamableResourceSource
+{
+    private final int compressionCutoff;
+
+    private final StreamableResourceSource delegate;
+
+    public SRSCompressingInterceptor(int compressionCutoff, StreamableResourceSource delegate)
+    {
+        this.compressionCutoff = compressionCutoff;
+        this.delegate = delegate;
+    }
+
+    public StreamableResource getStreamableResource(Resource baseResource, Set<StreamableResourceFeature> features)
+            throws IOException
+    {
+        StreamableResource streamable = delegate.getStreamableResource(baseResource, features);
+
+        if (streamable.getCompression() == CompressionStatus.COMPRESSABLE
+                && features.contains(StreamableResourceFeature.GZIP_COMPRESSION)) { return compress(streamable); }
+
+        return streamable;
+    }
+
+    private StreamableResource compress(StreamableResource uncompressed) throws IOException
+    {
+        int size = uncompressed.getSize();
+
+        // Because of GZIP overhead, streams below a certain point actually get larger when compressed so
+        // we don't even try.
+
+        if (size < compressionCutoff)
+            return uncompressed;
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(size);
+
+        GZIPOutputStream gos = new GZIPOutputStream(bos);
+        BufferedOutputStream buffered = new BufferedOutputStream(gos);
+
+        uncompressed.streamTo(buffered);
+
+        buffered.close();
+
+        BytestreamCache cache = new BytestreamCache(bos);
+
+        return new StreamableResourceImpl(uncompressed.getContentType(), CompressionStatus.COMPRESSED, cache);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java Tue Mar  1 19:29:08 2011
@@ -16,7 +16,6 @@ package org.apache.tapestry5.internal.se
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
@@ -29,10 +28,9 @@ import java.util.zip.GZIPOutputStream;
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.internal.services.ResourceCache;
-import org.apache.tapestry5.internal.services.StreamableResource;
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.json.JSONArray;
@@ -42,12 +40,15 @@ import org.apache.tapestry5.services.Req
 import org.apache.tapestry5.services.Response;
 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceFeature;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
 
 public class StackAssetRequestHandler implements AssetRequestHandler, InvalidationListener
 {
-    private final ResourceCache resourceCache;
+    private final StreamableResourceSource streamableResourceSource;
 
     private final JavaScriptStackSource javascriptStackSource;
 
@@ -64,19 +65,26 @@ public class StackAssetRequestHandler im
 
     private final Map<String, BytestreamCache> compressedCache = CollectionFactory.newCaseInsensitiveMap();
 
-    public StackAssetRequestHandler(ResourceCache resourceCache, JavaScriptStackSource javascriptStackSource,
-            LocalizationSetter localizationSetter, ResponseCompressionAnalyzer compressionAnalyzer,
+    public StackAssetRequestHandler(StreamableResourceSource streamableResourceSource,
+            JavaScriptStackSource javascriptStackSource, LocalizationSetter localizationSetter,
+            ResponseCompressionAnalyzer compressionAnalyzer,
 
             @Symbol(SymbolConstants.PRODUCTION_MODE)
             boolean productionMode)
     {
-        this.resourceCache = resourceCache;
+        this.streamableResourceSource = streamableResourceSource;
         this.javascriptStackSource = javascriptStackSource;
         this.localizationSetter = localizationSetter;
         this.compressionAnalyzer = compressionAnalyzer;
         this.productionMode = productionMode;
     }
 
+    @PostInjection
+    public void listenToInvalidations(ResourceChangeTracker resourceChangeTracker)
+    {
+        resourceChangeTracker.addInvalidationListener(this);
+    }
+
     public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
     {
         boolean compress = compressionAnalyzer.isGZipSupported();
@@ -92,6 +100,8 @@ public class StackAssetRequestHandler im
         if (productionMode)
             response.setDateHeader("Expires", lastModified + InternalConstants.TEN_YEARS);
 
+        response.disableCompression();
+
         response.setContentLength(cachedStream.size());
 
         if (compress)
@@ -201,11 +211,10 @@ public class StackAssetRequestHandler im
     {
         Resource resource = library.getResource();
 
-        StreamableResource streamable = resourceCache.getStreamableResource(resource);
-
-        InputStream inputStream = streamable.getStream(false);
+        StreamableResource streamable = streamableResourceSource.getStreamableResource(resource,
+                StreamableResourceFeature.NONE);
 
-        TapestryInternalUtils.copy(inputStream, outputStream);
+        streamable.streamTo(outputStream);
     }
 
     private BytestreamCache compressStream(BytestreamCache uncompressed) throws IOException

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,63 @@
+// Copyright 2011 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 java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.StreamableResource;
+
+public class StreamableResourceImpl implements StreamableResource
+{
+    private final String contentType;
+
+    private final CompressionStatus compression;
+
+    private final BytestreamCache bytestreamCache;
+
+    public StreamableResourceImpl(String contentType, CompressionStatus compression, BytestreamCache bytestreamCache)
+    {
+        this.contentType = contentType;
+        this.compression = compression;
+        this.bytestreamCache = bytestreamCache;
+    }
+
+    public CompressionStatus getCompression()
+    {
+        return compression;
+    }
+
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+    public int getSize()
+    {
+        return bytestreamCache.size();
+    }
+
+    public void streamTo(OutputStream os) throws IOException
+    {
+        bytestreamCache.writeTo(os);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("StreamableResource<%s %s size: %d>", contentType, compression.name(), getSize());
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,96 @@
+// Copyright 2011 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 java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.ContentTypeAnalyzer;
+import org.apache.tapestry5.services.assets.ResourceTransformer;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceFeature;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+
+public class StreamableResourceSourceImpl implements StreamableResourceSource
+{
+    private final Map<String, ResourceTransformer> configuration;
+
+    private final ContentTypeAnalyzer contentTypeAnalyzer;
+
+    private final CompressionAnalyzer compressionAnalyzer;
+
+    public StreamableResourceSourceImpl(Map<String, ResourceTransformer> configuration,
+            ContentTypeAnalyzer contentTypeAnalyzer, CompressionAnalyzer compressionAnalyzer)
+    {
+        this.configuration = configuration;
+        this.contentTypeAnalyzer = contentTypeAnalyzer;
+        this.compressionAnalyzer = compressionAnalyzer;
+    }
+
+    public StreamableResource getStreamableResource(Resource baseResource, Set<StreamableResourceFeature> features)
+            throws IOException
+    {
+        assert baseResource != null;
+
+        URL url = baseResource.toURL();
+
+        if (url == null)
+            throw new IOException(String.format("Resource %s does not exist.", baseResource));
+
+        String fileSuffix = TapestryInternalUtils.toFileSuffix(baseResource.getFile());
+
+        // Optionally, transform the resource. The main driver for this is to allow
+        // for libraries like LessJS (http://lesscss.org/) or
+        // http://jashkenas.github.com/coffee-script/
+        ResourceTransformer rt = configuration.get(fileSuffix);
+
+        InputStream buffered = new BufferedInputStream(url.openStream());
+
+        InputStream transformed = rt == null ? buffered : rt.transform(buffered);
+
+        BytestreamCache bytestreamCache = readStream(transformed);
+
+        transformed.close();
+        buffered.close();
+
+        String contentType = contentTypeAnalyzer.getContentType(baseResource);
+
+        boolean compressable = compressionAnalyzer.isCompressable(contentType);
+
+        return new StreamableResourceImpl(contentType, compressable ? CompressionStatus.COMPRESSABLE
+                : CompressionStatus.NOT_COMPRESSABLE, bytestreamCache);
+    }
+
+    private BytestreamCache readStream(InputStream stream) throws IOException
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+        TapestryInternalUtils.copy(stream, bos);
+
+        stream.close();
+
+        return new BytestreamCache(bos);
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ResponseCompressionAnalyzer.java Tue Mar  1 19:29:08 2011
@@ -1,10 +1,10 @@
-// Copyright 2009 The Apache Software Foundation
+// Copyright 2009, 2011 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
+// 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,
@@ -15,12 +15,11 @@
 package org.apache.tapestry5.services;
 
 import org.apache.tapestry5.ioc.annotations.UsesConfiguration;
+import org.apache.tapestry5.services.assets.CompressionAnalyzer;
 
 /**
  * Used to determine if the client supports GZIP compression of the response.
- * <p/>
- * The configuration is an unordered list of content types that should <em>not</em> be compressed.
- *
+ * 
  * @since 5.1.0.0
  */
 @UsesConfiguration(String.class)
@@ -28,7 +27,7 @@ public interface ResponseCompressionAnal
 {
     /**
      * Checks the Accept-Encoding request header for a "gzip" token.
-     *
+     * 
      * @return true if gzip is supported by client
      */
     boolean isGZipSupported();
@@ -38,9 +37,12 @@ public interface ResponseCompressionAnal
      * through a GZip filter consumes cycles and makes them larger.
      * <p/>
      * Contribute content type strings to the service's configuration to mark them as not compressable.
-     *
-     * @param contentType the mime type of the content, such as "text/html" or "image/jpeg".
-     * @return true if compression is worthwile
+     * 
+     * @param contentType
+     *            the mime type of the content, such as "text/html" or "image/jpeg".
+     * @return true if compression is worthwhile
+     * @deprecated Deprecated in Tapestry 5.3. This method is to be removed at a later date. The service's configuration
+     *             is no longer used. Instead, contribute to and use {@link CompressionAnalyzer}.
      */
     boolean isCompressable(String contentType);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=1075990&r1=1075989&r2=1075990&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Tue Mar  1 19:29:08 2011
@@ -226,6 +226,7 @@ import org.apache.tapestry5.runtime.Rend
 import org.apache.tapestry5.services.ajax.MultiZoneUpdateEventResultProcessor;
 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.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -255,7 +256,8 @@ import org.slf4j.Logger;
  * The root module for Tapestry.
  */
 @Marker(Core.class)
-@SubModule(InternalModule.class)
+@SubModule(
+{ InternalModule.class, AssetsModule.class })
 public final class TapestryModule
 {
     private final PipelineBuilder pipelineBuilder;
@@ -288,7 +290,7 @@ public final class TapestryModule
      * these service are defined by the module itself, that's ok because
      * services are always lazy proxies). This isn't
      * about efficiency (it may be slightly more efficient, but not in any
-     * noticable way), it's about eliminating the
+     * noticeable way), it's about eliminating the
      * need to keep injecting these dependencies into individual service builder
      * and contribution methods.
      */
@@ -332,7 +334,7 @@ public final class TapestryModule
 
     // A bunch of classes "promoted" from inline inner class to nested classes,
     // just so that the stack trace would be more readable. Most of these
-    // are teminators for pipeline services.
+    // are terminators for pipeline services.
 
     /**
      * @since 5.1.0.0
@@ -2505,34 +2507,6 @@ public final class TapestryModule
     }
 
     /**
-     * Adds content types:
-     * <dl>
-     * <dt>css</dt>
-     * <dd>text/css</dd>
-     * <dt>js</dt>
-     * <dd>text/javascript</dd>
-     * <dt>jpg, jpeg</dt>
-     * <dd>image/jpeg</dd>
-     * <dt>gif</dt>
-     * <dd>image/gif</dd>
-     * <dt>png</dt>
-     * <dd>image/png</dd>
-     * <dt>swf</dt>
-     * <dd>application/x-shockwave-flash</dd>
-     * </dl>
-     */
-    public void contributeResourceStreamer(MappedConfiguration<String, String> configuration)
-    {
-        configuration.add("css", "text/css");
-        configuration.add("js", "text/javascript");
-        configuration.add("gif", "image/gif");
-        configuration.add("jpg", "image/jpeg");
-        configuration.add("jpeg", "image/jpeg");
-        configuration.add("png", "image/png");
-        configuration.add("swf", "application/x-shockwave-flash");
-    }
-
-    /**
      * Adds a listener to the {@link org.apache.tapestry5.internal.services.ComponentInstantiatorSource} that clears the
      * {@link PropertyAccess} and {@link TypeCoercer} caches on
      * a class loader invalidation. In addition, forces the
@@ -2756,26 +2730,6 @@ public final class TapestryModule
     }
 
     /**
-     * Contributions are content types that do not benefit from compression. Adds
-     * the following content types:
-     * <ul>
-     * <li>image/jpeg</li>
-     * <li>image/gif</li>
-     * <li>image/png</li>
-     * <li>application/x-shockwave-flash</li>
-     * </ul>
-     * 
-     * @since 5.1.0.0
-     */
-    public static void contributeResponseCompressionAnalyzer(Configuration<String> configuration)
-    {
-        configuration.add("image/jpeg");
-        configuration.add("image/gif");
-        configuration.add("image/png");
-        configuration.add("application/x-shockwave-flash");
-    }
-
-    /**
      * @since 5.1.1.0
      */
     @Marker(Primary.class)

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,124 @@
+// Copyright 2011 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.SymbolConstants;
+import org.apache.tapestry5.internal.services.assets.CompressionAnalyzerImpl;
+import org.apache.tapestry5.internal.services.assets.ContentTypeAnalyzerImpl;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTrackerImpl;
+import org.apache.tapestry5.internal.services.assets.SRSCachingInterceptor;
+import org.apache.tapestry5.internal.services.assets.SRSCompressedCachingInterceptor;
+import org.apache.tapestry5.internal.services.assets.SRSCompressingInterceptor;
+import org.apache.tapestry5.internal.services.assets.StreamableResourceSourceImpl;
+import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Decorate;
+import org.apache.tapestry5.ioc.annotations.Marker;
+import org.apache.tapestry5.ioc.annotations.Order;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.services.Core;
+
+/**
+ * @since 5.3.0
+ */
+@Marker(Core.class)
+public class AssetsModule
+{
+    public static void bind(ServiceBinder binder)
+    {
+        binder.bind(StreamableResourceSource.class, StreamableResourceSourceImpl.class);
+        binder.bind(CompressionAnalyzer.class, CompressionAnalyzerImpl.class);
+        binder.bind(ContentTypeAnalyzer.class, ContentTypeAnalyzerImpl.class);
+        binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
+    }
+
+    // The use of decorators is to allow third-parties to get their own extensions
+    // into the pipeline.
+
+    @Decorate(id = "GZipCompression", serviceInterface = StreamableResourceSource.class)
+    public StreamableResourceSource enableCompression(StreamableResourceSource delegate,
+            @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
+            boolean gzipEnabled, @Symbol(SymbolConstants.MIN_GZIP_SIZE)
+            int compressionCutoff)
+    {
+        return gzipEnabled ? new SRSCompressingInterceptor(compressionCutoff, delegate) : null;
+    }
+
+    @Decorate(id = "CacheCompressed", serviceInterface = StreamableResourceSource.class)
+    @Order("before:GZIpCompression")
+    public StreamableResourceSource enableCompressedCaching(StreamableResourceSource delegate,
+            @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
+            boolean gzipEnabled, ResourceChangeTracker tracker)
+    {
+        return gzipEnabled ? new SRSCompressedCachingInterceptor(tracker, delegate) : null;
+    }
+
+    @Decorate(id = "Cache", serviceInterface = StreamableResourceSource.class)
+    @Order("after:GZipCompression")
+    public StreamableResourceSource enableUncompressedCaching(StreamableResourceSource delegate,
+            ResourceChangeTracker tracker)
+    {
+        return new SRSCachingInterceptor(tracker, delegate);
+    }
+
+    /**
+     * Adds content types:
+     * <dl>
+     * <dt>css</dt>
+     * <dd>text/css</dd>
+     * <dt>js</dt>
+     * <dd>text/javascript</dd>
+     * <dt>jpg, jpeg</dt>
+     * <dd>image/jpeg</dd>
+     * <dt>gif</dt>
+     * <dd>image/gif</dd>
+     * <dt>png</dt>
+     * <dd>image/png</dd>
+     * <dt>swf</dt>
+     * <dd>application/x-shockwave-flash</dd>
+     * </dl>
+     */
+    @Contribute(ContentTypeAnalyzer.class)
+    public void setupDefaultContentTypeMappings(MappedConfiguration<String, String> configuration)
+    {
+        configuration.add("css", "text/css");
+        configuration.add("js", "text/javascript");
+        configuration.add("gif", "image/gif");
+        configuration.add("jpg", "image/jpeg");
+        configuration.add("jpeg", "image/jpeg");
+        configuration.add("png", "image/png");
+        configuration.add("swf", "application/x-shockwave-flash");
+    }
+
+    /**
+     * Disables compression for the following content types:
+     * <ul>
+     * <li>image/jpeg</li>
+     * <li>image/gif</li>
+     * <li>image/png</li>
+     * <li>application/x-shockwave-flash</li>
+     * </ul>
+     */
+    @Contribute(CompressionAnalyzer.class)
+    public void disableCompressionForImageTypes(MappedConfiguration<String, Boolean> configuration)
+    {
+        configuration.add("image/jpeg", false);
+        configuration.add("image/gif", false);
+        configuration.add("image/png", false);
+        configuration.add("application/x-shockwave-flash", false);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionAnalyzer.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionAnalyzer.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionAnalyzer.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,38 @@
+// Copyright 2011 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.annotations.UsesMappedConfiguration;
+
+/**
+ * Identifies which content types are compressable. In general, content types are assumed to be compressable. The
+ * configuration of the service identifies exceptions, which are usually image file formats.
+ * <p>
+ * The configuration maps content types to boolean values (true for compressable).
+ * 
+ * @since 5.3.0
+ */
+@UsesMappedConfiguration(boolean.class)
+public interface CompressionAnalyzer
+{
+    /**
+     * For a given MIME type, is the content compressable via GZip?
+     * 
+     * @param contentType
+     *            MIME content type, possibly included attributes such as encoding type
+     * @return true if the content is not "naturally" compressed
+     */
+    boolean isCompressable(String contentType);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionStatus.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionStatus.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionStatus.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/CompressionStatus.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,42 @@
+// Copyright 2011 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.services.Response;
+
+/**
+ * Indicates how the content inside a {@link StreamableResource} is (potentially) compressed.
+ * 
+ * @since 5.3.0
+ */
+public enum CompressionStatus
+{
+    /**
+     * The content may be compressed but has not yet been compressed. This is true for most text-oriented content types,
+     * but not found most image content types.
+     */
+    COMPRESSABLE,
+
+    /**
+     * The content has been compressed, which must be reflected in the {@link Response}'s content encoding.
+     */
+    COMPRESSED,
+
+    /**
+     * The content is not compressable. This is usually the case for image content types, where the structure
+     * of the content already includes compression.
+     */
+    NOT_COMPRESSABLE;
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ContentTypeAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ContentTypeAnalyzer.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ContentTypeAnalyzer.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ContentTypeAnalyzer.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,39 @@
+// Copyright 2011 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 org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
+import org.apache.tapestry5.services.Context;
+
+/**
+ * Used to determine the MIME content type for a resource. The service configuration is the first step,
+ * followed by {@link Context#getMimeType(String)}, and then (finally) "application/octet-stream"
+ * as a stop-gap.
+ * <p>
+ * The service configuration maps the file extension (e.g., "png") to its corresponding MIME type (e.g., "image/png");
+ */
+@UsesMappedConfiguration(String.class)
+public interface ContentTypeAnalyzer
+{
+    /**
+     * Analyze the resource to determine its content type.
+     * 
+     * @param resource
+     *            to analyze
+     * @return a MIME content type
+     */
+    String getContentType(Resource resource);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,23 @@
+// Copyright 2011 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 java.io.IOException;
+import java.io.InputStream;
+
+public interface ResourceTransformer
+{
+    InputStream transform(InputStream source) throws IOException;
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResource.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,46 @@
+// Copyright 2011 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 java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.tapestry5.ioc.Resource;
+
+/**
+ * An object, derived from a {@link Resource}, that can be streamed (ultimately, to a client web browser).
+ * 
+ * @since 5.3.0
+ */
+public interface StreamableResource
+{
+    CompressionStatus getCompression();
+
+    /**
+     * Returns the MIME content type, e.g., "image/jpeg".
+     */
+    String getContentType();
+
+    /**
+     * The size, in bytes, of the underlying bytestream.
+     */
+    int getSize();
+
+    /**
+     * Streams the resource's content to the provided stream. The caller is responsible for flushing or closing
+     * the output stream.
+     */
+    void streamTo(OutputStream os) throws IOException;
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceFeature.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceFeature.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceFeature.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceFeature.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,67 @@
+// Copyright 2011 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 java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.javascript.JavaScriptStack;
+
+/**
+ * Defines additional features desired when accessing the content of a {@link Resource} as
+ * a {@link StreamableResource}.
+ * 
+ * @since 5.3.0
+ * @see StreamableResourceSource#getStreamableResource(Resource, Set)
+ */
+public enum StreamableResourceFeature
+{
+    /**
+     * The content may be GZIP compressed (if its content type is {@linkplain CompressionAnalyzer compressable}).
+     */
+    GZIP_COMPRESSION,
+
+    /**
+     * The content may be cached. This is generally desired, except when the content is being accessed so that
+     * it can be aggregated with other content (a {@link JavaScriptStack} is the canonical example) and the individual
+     * resources are not accessed except when aggregated. There are two layers of caching: for uncompressed content, and
+     * for compressed content (where the content is compressable).
+     */
+    CACHING,
+
+    /**
+     * Applies to certain content types (specifically, JavaScript and CSS) where the content can be reduced in size
+     * without changing its effective content (i.e., remove unnecessary whitespace, comments, simplify names, etc.).
+     */
+    MINIMIZATION;
+
+    /**
+     * Unmodifiable set of all features.
+     */
+    public static final Set<StreamableResourceFeature> ALL = Collections.unmodifiableSet(EnumSet
+            .allOf(StreamableResourceFeature.class));
+
+    /**
+     * Unmodifiable set of all features, excluding {@link #GZIP_COMPRESSION}.
+     */
+    public static final Set<StreamableResourceFeature> NO_COMPRESSION = Collections.unmodifiableSet(EnumSet.range(
+            CACHING, MINIMIZATION));
+
+    /** Unmodifiable and empty. */
+    public static final Set<StreamableResourceFeature> NONE = Collections.unmodifiableSet(EnumSet
+            .noneOf(StreamableResourceFeature.class));
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceSource.java?rev=1075990&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceSource.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/StreamableResourceSource.java Tue Mar  1 19:29:08 2011
@@ -0,0 +1,49 @@
+// Copyright 2011 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 java.io.IOException;
+import java.util.Set;
+
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.annotations.Primary;
+import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
+
+/**
+ * Converts {@link Resource}s into {@link StreamableResource}s, and may be responsible for
+ * {@linkplain ResourceTransformer transforming} resources based on file extension. Only
+ * the {@link Primary} service has a configuration; the alternate {@link ExtendedProcessing} service
+ * adds caching, compression, and minimization.
+ * 
+ * @since 5.3.0
+ */
+@UsesMappedConfiguration(ResourceTransformer.class)
+public interface StreamableResourceSource
+{
+    /**
+     * Converts a Resource (which must be non-null and exist) into a streamable resource, along with
+     * some additional optional behaviors.
+     * 
+     * @param baseResource
+     *            the resource to convert
+     * @param features
+     *            a set of features desired for the resource, normally {@link StreamableResourceFeature#ALL}
+     * @return the contents of the Resource, possibly transformed, in a streamable format.
+     * @throws IOException
+     *             if the resource does not exist or a URL for the content is not available
+     */
+    StreamableResource getStreamableResource(Resource baseResource, Set<StreamableResourceFeature> features)
+            throws IOException;
+}