Author: hlship
Date: Mon Apr 19 14:10:14 2010
New Revision: 935588

URL: http://svn.apache.org/viewvc?rev=935588&view=rev
Log:
Provide the missing asset request handler for the virtual "stack" folder.

Added:
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
   (with props)
Modified:
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestConstants.java
    
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/services/TapestryModule.java

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java?rev=935588&r1=935587&r2=935588&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
 Mon Apr 19 14:10:14 2010
@@ -137,20 +137,24 @@ public final class InternalConstants
      * @since 5.1.0.0
      */
     public static final String GZIP_CONTENT_ENCODING = "gzip";
+
     /**
      * Identifies the start of an expansion inside a template.
      */
     public static final String EXPANSION_START = "${";
+
     /**
      * Special prefix for parameters that are inherited from named parameters 
of their container.
      */
     public static final String INHERIT_BINDING_PREFIX = "inherit:";
+
     public static final long TEN_YEARS = new 
TimeInterval("10y").milliseconds();
 
     public static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     /**
-     * Name of the core {...@link JavascriptStack}.
+     * Name of the core {...@link JavascriptStack}, which supplies the basic 
JavaScript infrastructure
+     * on the client.
      * 
      * @since 5.2.0
      */

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestConstants.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestConstants.java?rev=935588&r1=935587&r2=935588&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestConstants.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestConstants.java
 Mon Apr 19 14:10:14 2010
@@ -38,14 +38,6 @@ public final class RequestConstants
     public static final String CONTEXT_FOLDER = "ctx";
 
     /**
-     * Folder for virtual assets: combined JavaScript files. The file name is 
actually a compressed bytestream
-     * of the names of each file.
-     * 
-     * @since 5.1.0.2
-     */
-    public static final String VIRTUAL_FOLDER = "virtual";
-
-    /**
      * Folder for combined {...@link JavascriptStack} JavaScript files. The 
path consists of the locale (as a folder) and
      * the name
      * of the stack (suffixed with ".js").

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResource.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResource.java?rev=935588&r1=935587&r2=935588&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResource.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamableResource.java
 Mon Apr 19 14:10:14 2010
@@ -1,4 +1,4 @@
-// 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.
@@ -19,10 +19,11 @@ import java.io.InputStream;
 
 /**
  * Abstracts around a {...@link org.apache.tapestry5.ioc.Resource} to allow 
access to the resource's content either
- * compressed on uncompressed. The advantage is that, for cmpressed streams, 
the data is only compressed once, rather
+ * compressed on uncompressed. The advantage is that, for compressed streams, 
the data is only compressed once, rather
  * than for each request.
  *
  * @since 5.1.0.0
+ * @see ResourceCache
  */
 public interface StreamableResource
 {

Added: 
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=935588&view=auto
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
 (added)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
 Mon Apr 19 14:10:14 2010
@@ -0,0 +1,226 @@
+// Copyright 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
+//
+// 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.json.JSONArray;
+import org.apache.tapestry5.services.InvalidationListener;
+import org.apache.tapestry5.services.LocalizationSetter;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
+import org.apache.tapestry5.services.javascript.JavascriptStack;
+import org.apache.tapestry5.services.javascript.JavascriptStackSource;
+
+public class StackAssetRequestHandler implements AssetRequestHandler, 
InvalidationListener
+{
+    private final ResourceCache resourceCache;
+
+    private final JavascriptStackSource javascriptStackSource;
+
+    private final LocalizationSetter localizationSetter;
+
+    private final ResponseCompressionAnalyzer compressionAnalyzer;
+
+    private final boolean productionMode;
+
+    private final Pattern pathPattern = Pattern.compile("^(.+)/(.+)\\.js$");
+
+    // Two caches, keyed on extra path. Both are accessed only from 
synchronized blocks.
+    private final Map<String, ByteArrayOutputStream> uncompressedCache = 
CollectionFactory.newCaseInsensitiveMap();
+
+    private final Map<String, ByteArrayOutputStream> compressedCache = 
CollectionFactory.newCaseInsensitiveMap();
+
+    public StackAssetRequestHandler(ResourceCache resourceCache, 
JavascriptStackSource javascriptStackSource,
+            LocalizationSetter localizationSetter, ResponseCompressionAnalyzer 
compressionAnalyzer,
+
+            @Symbol(SymbolConstants.PRODUCTION_MODE)
+            boolean productionMode)
+    {
+        this.resourceCache = resourceCache;
+        this.javascriptStackSource = javascriptStackSource;
+        this.localizationSetter = localizationSetter;
+        this.compressionAnalyzer = compressionAnalyzer;
+        this.productionMode = productionMode;
+    }
+
+    public boolean handleAssetRequest(Request request, Response response, 
String extraPath) throws IOException
+    {
+        boolean compress = compressionAnalyzer.isGZipSupported();
+
+        ByteArrayOutputStream stream = getStream(extraPath, compress);
+
+        // The whole point of this is to force the client to aggressively 
cache the combined, virtual
+        // stack asset.
+
+        long lastModified = System.currentTimeMillis();
+        response.setDateHeader("Last-Modified", lastModified);
+
+        if (productionMode)
+            response.setDateHeader("Expires", lastModified + 
InternalConstants.TEN_YEARS);
+
+        response.setContentLength(stream.size());
+
+        // Inform the upper layers that we are controlled compression here.
+        request.setAttribute(InternalConstants.SUPPRESS_COMPRESSION, true);
+
+        if (compress)
+            response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, 
InternalConstants.GZIP_CONTENT_ENCODING);
+
+        // CSS support is problematic, because of relative URLs inside the CSS 
files. For the
+        // moment, only JavaScript is supported.
+
+        OutputStream output = response.getOutputStream("text/javascript");
+
+        stream.writeTo(output);
+
+        output.close();
+
+        return true;
+    }
+
+    /** Notified by the {...@link ResourceCache} when resource files change; 
the internal caches are cleared. */
+    public synchronized void objectWasInvalidated()
+    {
+        uncompressedCache.clear();
+        compressedCache.clear();
+    }
+
+    private ByteArrayOutputStream getStream(String extraPath, boolean 
compressed) throws IOException
+    {
+        return compressed ? getCompressedStream(extraPath) : 
getUncompressedStream(extraPath);
+    }
+
+    private synchronized ByteArrayOutputStream getCompressedStream(String 
extraPath) throws IOException
+    {
+        ByteArrayOutputStream result = compressedCache.get(extraPath);
+
+        if (result == null)
+        {
+            ByteArrayOutputStream uncompressed = 
getUncompressedStream(extraPath);
+            result = compressStream(uncompressed);
+            compressedCache.put(extraPath, result);
+        }
+
+        return result;
+    }
+
+    private synchronized ByteArrayOutputStream getUncompressedStream(String 
extraPath) throws IOException
+    {
+        ByteArrayOutputStream result = uncompressedCache.get(extraPath);
+
+        if (result == null)
+        {
+            result = assembleStackContent(extraPath);
+            uncompressedCache.put(extraPath, result);
+        }
+
+        return result;
+    }
+
+    private ByteArrayOutputStream assembleStackContent(String extraPath) 
throws IOException
+    {
+        Matcher matcher = pathPattern.matcher(extraPath);
+
+        if (!matcher.matches())
+            throw new RuntimeException("Invalid path for a stack asset 
request.");
+
+        String localeName = matcher.group(1);
+        String stackName = matcher.group(2);
+
+        return assembleStackContent(localeName, stackName);
+    }
+
+    private ByteArrayOutputStream assembleStackContent(String localeName, 
String stackName) throws IOException
+    {
+        localizationSetter.setNonPeristentLocaleFromLocaleName(localeName);
+
+        JavascriptStack stack = javascriptStackSource.getStack(stackName);
+        List<Asset> libraries = stack.getJavascriptLibraries();
+
+        return assembleStackContent(libraries);
+    }
+
+    private ByteArrayOutputStream assembleStackContent(List<Asset> libraries) 
throws IOException
+    {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        OutputStreamWriter osw = new OutputStreamWriter(result, "UTF-8");
+        PrintWriter writer = new PrintWriter(osw, true);
+
+        JSONArray paths = new JSONArray();
+
+        for (Asset library : libraries)
+        {
+            String path = library.toClientURL();
+
+            paths.put(path);
+
+            writer.format("\n/* %s */;\n", path);
+
+            streamLibraryContent(library, result);
+        }
+
+        writer.format("\n;/**/\nTapestry.markScriptLibrariesLoaded(%s);\n", 
paths);
+
+        writer.close();
+
+        return result;
+    }
+
+    private void streamLibraryContent(Asset library, OutputStream 
outputStream) throws IOException
+    {
+        Resource resource = library.getResource();
+
+        StreamableResource streamable = 
resourceCache.getStreamableResource(resource);
+
+        InputStream inputStream = streamable.getStream(false);
+
+        TapestryInternalUtils.copy(inputStream, outputStream);
+    }
+
+    private ByteArrayOutputStream compressStream(ByteArrayOutputStream 
uncompressed) throws IOException
+    {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        OutputStream compressor = new GZIPOutputStream(result);
+
+        uncompressed.writeTo(compressor);
+
+        compressor.close();
+
+        return result;
+    }
+
+}

Propchange: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

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=935588&r1=935587&r2=935588&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
 Mon Apr 19 14:10:14 2010
@@ -80,6 +80,7 @@ import org.apache.tapestry5.internal.ser
 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.javascript.CoreJavascriptStack;
 import 
org.apache.tapestry5.internal.services.javascript.JavascriptStackPathConstructor;
 import 
org.apache.tapestry5.internal.services.javascript.JavascriptStackSourceImpl;
@@ -476,9 +477,16 @@ public final class TapestryModule
     @ContextProvider
     AssetFactory contextAssetFactory,
 
+    @Autobuild
+    StackAssetRequestHandler stackAssetRequestHandler,
+
+    ResourceCache resourceCache,
+
     ClasspathAssetAliasManager classpathAssetAliasManager, ResourceStreamer 
streamer,
             AssetResourceLocator assetResourceLocator)
     {
+        resourceCache.addInvalidationListener(stackAssetRequestHandler);
+
         Map<String, String> mappings = 
classpathAssetAliasManager.getMappings();
 
         for (String folder : mappings.keySet())
@@ -490,6 +498,9 @@ public final class TapestryModule
 
         configuration.add(RequestConstants.CONTEXT_FOLDER, new 
ContextAssetRequestHandler(streamer, contextAssetFactory
                 .getRootResource()));
+
+        configuration.add(RequestConstants.STACK_FOLDER, 
stackAssetRequestHandler);
+
     }
 
     private static String toPackagePath(String packageName)


Reply via email to