You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2013/03/15 00:09:18 UTC
[9/14] git commit: Add content checksum to URL for combined content
of JavaScript stacks
Add content checksum to URL for combined content of JavaScript stacks
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/373f5cf5
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/373f5cf5
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/373f5cf5
Branch: refs/heads/master
Commit: 373f5cf52a5a8c6b65a273c25815c399e75060ec
Parents: ceb220e
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Thu Mar 14 11:34:07 2013 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Thu Mar 14 13:41:12 2013 -0700
----------------------------------------------------------------------
.../services/ClasspathAssetAliasManagerImpl.java | 11 +-
.../internal/services/ContextAssetFactory.java | 20 ++-
.../internal/services/ResourceStreamerImpl.java | 4 +-
.../services/assets/AssetPathConstructorImpl.java | 34 +++-
.../services/assets/JavaScriptStackAssembler.java | 39 ++++
.../assets/JavaScriptStackAssemblerImpl.java | 159 ++++++++++++++
.../services/assets/StackAssetRequestHandler.java | 170 ++------------
.../JavaScriptStackPathConstructorImpl.java | 40 +++-
.../services/assets/AssetPathConstructor.java | 20 ++-
.../tapestry5/services/assets/AssetsModule.java | 1 +
.../tapestry5/integration/app1/LibraryTests.groovy | 6 +-
.../internal/services/ContextAssetFactoryTest.java | 4 +-
12 files changed, 332 insertions(+), 176 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
index 69cb15c..7a04a87 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
@@ -16,11 +16,13 @@ package org.apache.tapestry5.internal.services;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.util.AvailableValues;
import org.apache.tapestry5.ioc.util.UnknownValueException;
import org.apache.tapestry5.services.ClasspathAssetAliasManager;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -103,7 +105,14 @@ public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManage
String virtualPath = resourcePath.substring(pathPrefix.length() + 1);
- return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath, resource);
+ try
+ {
+ return assetPathConstructor.constructAssetPath(virtualFolder, virtualPath, resource);
+ } catch (IOException ex)
+ {
+ throw new RuntimeException(String.format("Unable to construct asset path for %s/%s (from %s): %s",
+ virtualFolder, virtualPath, resource, InternalUtils.toMessage(ex)), ex);
+ }
}
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
index b7e41e2..bd6c6b3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextAssetFactory.java
@@ -16,11 +16,14 @@ package org.apache.tapestry5.internal.services;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.AssetFactory;
import org.apache.tapestry5.services.AssetPathConverter;
import org.apache.tapestry5.services.Context;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import java.io.IOException;
+
/**
* Implementation of {@link AssetFactory} for assets that are part of the web application context.
*
@@ -49,14 +52,21 @@ public class ContextAssetFactory implements AssetFactory
public Asset createAsset(Resource resource)
{
- String defaultPath = assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath(), resource);
+ try
+ {
+ String defaultPath = assetPathConstructor.constructAssetPath(RequestConstants.CONTEXT_FOLDER, resource.getPath(), resource);
+
+ if (invariant)
+ {
+ return createInvariantAsset(resource, defaultPath);
+ }
- if (invariant)
+ return createVariantAsset(resource, defaultPath);
+ } catch (IOException ex)
{
- return createInvariantAsset(resource, defaultPath);
+ throw new RuntimeException(String.format("Unable to construct asset path for %s: %s",
+ resource, InternalUtils.toMessage(ex)), ex);
}
-
- return createVariantAsset(resource, defaultPath);
}
private Asset createInvariantAsset(final Resource resource, final String defaultPath)
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
index 04c2a65..c4a6464 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceStreamerImpl.java
@@ -62,7 +62,9 @@ public class ResourceStreamerImpl implements ResourceStreamer
OperationTracker tracker,
@Symbol(SymbolConstants.PRODUCTION_MODE)
- boolean productionMode, ResourceChangeTracker resourceChangeTracker)
+ boolean productionMode,
+
+ ResourceChangeTracker resourceChangeTracker)
{
this.request = request;
this.response = response;
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
index 159c854..6ba0d72 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetPathConstructorImpl.java
@@ -15,6 +15,7 @@
package org.apache.tapestry5.internal.services.assets;
import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.internal.services.RequestConstants;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -23,6 +24,7 @@ import org.apache.tapestry5.services.PathConstructor;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResource;
import java.io.IOException;
@@ -60,7 +62,26 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
prefix = pathConstructor.constructClientPath(assetPathPrefix, "");
}
- public String constructAssetPath(String virtualFolder, String path, Resource resource)
+ public String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException
+ {
+ StringBuilder builder = new StringBuilder();
+
+ if (fullyQualified)
+ {
+ builder.append(baseURLSource.getBaseURL(request.isSecure()));
+ }
+
+ builder.append(prefix); // ends with a slash
+
+ builder.append(RequestConstants.STACK_FOLDER).append("/");
+ builder.append(assetChecksumGenerator.generateChecksum(resource)).append("/");
+ builder.append(localeName).append("/");
+ builder.append(path);
+
+ return builder.toString();
+ }
+
+ public String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException
{
assert InternalUtils.isNonBlank(virtualFolder);
assert path != null;
@@ -76,14 +97,11 @@ public class AssetPathConstructorImpl implements AssetPathConstructor
builder.append(virtualFolder);
builder.append("/");
- try
- {
- builder.append(assetChecksumGenerator.generateChecksum(resource));
- } catch (IOException ex)
- {
- throw new RuntimeException(ex);
- }
+ builder.append(assetChecksumGenerator.generateChecksum(resource));
+ // TODO: Under what conditions would the path ever be blank? Is this allowed? It may have made sense
+ // in 5.3, to allow access to a folder (to allow the client to build relative URLs), but that
+ // is no longer true in 5.4 because of the checksum in the URL.
if (InternalUtils.isNonBlank(path))
{
builder.append('/');
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
new file mode 100644
index 0000000..d63d71c
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
@@ -0,0 +1,39 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.tapestry5.services.assets.StreamableResource;
+
+import java.io.IOException;
+
+/**
+ * Assembles the individual assets of a {@link org.apache.tapestry5.services.javascript.JavaScriptStack} into
+ * a single resource; this is needed to generate a checksum for the aggregated assets, and also to service the
+ * aggregated stack content.
+ *
+ * @see org.apache.tapestry5.SymbolConstants#COMBINE_SCRIPTS
+ * @since 5.4
+ */
+public interface JavaScriptStackAssembler
+{
+ /**
+ * Obtains th {@link org.apache.tapestry5.services.javascript.JavaScriptStack} by name, and then
+ * uses the {@link org.apache.tapestry5.services.assets.StreamableResourceSource} service to
+ * obtain the assets, which are combined togethers.
+ * <p/>
+ * Expects the {@linkplain org.apache.tapestry5.services.LocalizationSetter#setNonPersistentLocaleFromLocaleName(String) non-persistent locale} to be set before invoking!
+ */
+ StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
new file mode 100644
index 0000000..d84bb28
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
@@ -0,0 +1,159 @@
+// Copyright 2013 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.services.ThreadLocale;
+import org.apache.tapestry5.json.JSONArray;
+import org.apache.tapestry5.services.assets.CompressionStatus;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+import org.apache.tapestry5.services.javascript.JavaScriptStack;
+import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
+
+import java.io.*;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
+{
+ private static final String JAVASCRIPT_CONTENT_TYPE = "text/javascript";
+
+ private ThreadLocale threadLocale;
+
+ private final ResourceChangeTracker resourceChangeTracker;
+
+ private final StreamableResourceSource streamableResourceSource;
+
+ private final JavaScriptStackSource stackSource;
+
+ private final Map<String, StreamableResource> cache = CollectionFactory.newCaseInsensitiveMap();
+
+ // TODO: Support for minimization
+ // TODO: Support for aggregated CSS as well as aggregated JavaScript
+
+ public JavaScriptStackAssemblerImpl(ThreadLocale threadLocale, ResourceChangeTracker resourceChangeTracker, StreamableResourceSource streamableResourceSource, JavaScriptStackSource stackSource)
+ {
+ this.threadLocale = threadLocale;
+ this.resourceChangeTracker = resourceChangeTracker;
+ this.streamableResourceSource = streamableResourceSource;
+ this.stackSource = stackSource;
+
+ resourceChangeTracker.clearOnInvalidation(cache);
+ }
+
+ @Override
+ public StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress) throws IOException
+ {
+ Locale locale = threadLocale.getLocale();
+
+ return assembleJavascriptResourceForStack(locale, stackName, compress);
+ }
+
+ private StreamableResource assembleJavascriptResourceForStack(Locale locale, String stackName, boolean compress) throws IOException
+ {
+ String key =
+ String.format("%s[%s] %s",
+ stackName,
+ compress ? "COMPRESS" : "UNCOMPRESSED",
+ locale.toString());
+
+ StreamableResource result = cache.get(key);
+
+ if (result == null)
+ {
+ result = assemble(locale, stackName, compress);
+ cache.put(key, result);
+ }
+
+ return result;
+ }
+
+ private StreamableResource assemble(Locale locale, String stackName, boolean compress) throws IOException
+ {
+ if (compress)
+ {
+ return compressStream(assembleJavascriptResourceForStack(locale, stackName, false));
+ }
+
+ JavaScriptStack stack = stackSource.getStack(stackName);
+
+ return assemble(locale.toString(), stackName, stack.getJavaScriptLibraries());
+ }
+
+
+ private StreamableResource assemble(String localeName, String stackName, List<Asset> libraries) throws IOException
+ {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
+ PrintWriter writer = new PrintWriter(osw, true);
+ long lastModified = 0;
+
+ StringBuilder description = new StringBuilder(String.format("'%s' JavaScript stack, for locale %s, resources=", stackName, localeName));
+ String sep = "";
+
+ JSONArray paths = new JSONArray();
+
+ for (Asset library : libraries)
+ {
+ String path = library.toClientURL();
+
+ paths.put(path);
+
+ writer.format("\n/* %s */;\n", path);
+
+ Resource resource = library.getResource();
+
+ description.append(sep).append(resource.toString());
+ sep = ", ";
+
+ StreamableResource streamable = streamableResourceSource.getStreamableResource(resource,
+ StreamableResourceProcessing.FOR_AGGREGATION, resourceChangeTracker);
+
+ streamable.streamTo(stream);
+
+ lastModified = Math.max(lastModified, streamable.getLastModified());
+ }
+
+ writer.close();
+
+ return new StreamableResourceImpl(
+ description.toString(),
+ JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSABLE, lastModified,
+ new BytestreamCache(stream));
+ }
+
+
+ private StreamableResource compressStream(StreamableResource uncompressed) throws IOException
+ {
+ ByteArrayOutputStream compressed = new ByteArrayOutputStream();
+ OutputStream compressor = new BufferedOutputStream(new GZIPOutputStream(compressed));
+
+ uncompressed.streamTo(compressor);
+
+ compressor.close();
+
+ BytestreamCache cache = new BytestreamCache(compressed);
+
+ return new StreamableResourceImpl(uncompressed.getDescription(), JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSED,
+ uncompressed.getLastModified(), cache);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
index 987e8e5..31c6b08 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
@@ -14,83 +14,52 @@
package org.apache.tapestry5.internal.services.assets;
-import org.apache.tapestry5.Asset;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.internal.services.ResourceStreamer;
import org.apache.tapestry5.ioc.OperationTracker;
-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;
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.*;
-import org.apache.tapestry5.services.javascript.JavaScriptStack;
+import org.apache.tapestry5.services.assets.AssetRequestHandler;
+import org.apache.tapestry5.services.assets.ResourceMinimizer;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
-import java.io.*;
-import java.util.List;
-import java.util.Map;
+import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.zip.GZIPOutputStream;
public class StackAssetRequestHandler implements AssetRequestHandler
{
- private static final String JAVASCRIPT_CONTENT_TYPE = "text/javascript";
-
- private final StreamableResourceSource streamableResourceSource;
-
- private final JavaScriptStackSource javascriptStackSource;
-
private final LocalizationSetter localizationSetter;
private final ResponseCompressionAnalyzer compressionAnalyzer;
private final ResourceStreamer resourceStreamer;
- private final Pattern pathPattern = Pattern.compile("^(.+)/(.+)\\.js$");
-
- // Two caches, keyed on extra path. Both are accessed only from synchronized blocks.
- private final Map<String, StreamableResource> uncompressedCache = CollectionFactory.newCaseInsensitiveMap();
-
- private final Map<String, StreamableResource> compressedCache = CollectionFactory.newCaseInsensitiveMap();
-
- private final ResourceMinimizer resourceMinimizer;
+ // Group 1: checksum
+ // Group 2: locale
+ // Group 3: path
+ private final Pattern pathPattern = Pattern.compile("^(.+)/(.+)/(.+)\\.js$");
private final OperationTracker tracker;
- private final boolean minificationEnabled;
-
- private final ResourceChangeTracker resourceChangeTracker;
+ private final JavaScriptStackAssembler javaScriptStackAssembler;
- public StackAssetRequestHandler(StreamableResourceSource streamableResourceSource,
- JavaScriptStackSource javascriptStackSource, LocalizationSetter localizationSetter,
- ResponseCompressionAnalyzer compressionAnalyzer, ResourceStreamer resourceStreamer,
- ResourceMinimizer resourceMinimizer, OperationTracker tracker,
-
- @Symbol(SymbolConstants.MINIFICATION_ENABLED)
- boolean minificationEnabled, ResourceChangeTracker resourceChangeTracker)
+ public StackAssetRequestHandler(LocalizationSetter localizationSetter,
+ ResponseCompressionAnalyzer compressionAnalyzer,
+ ResourceStreamer resourceStreamer,
+ OperationTracker tracker,
+ JavaScriptStackAssembler javaScriptStackAssembler)
{
- this.streamableResourceSource = streamableResourceSource;
- this.javascriptStackSource = javascriptStackSource;
this.localizationSetter = localizationSetter;
this.compressionAnalyzer = compressionAnalyzer;
this.resourceStreamer = resourceStreamer;
- this.resourceMinimizer = resourceMinimizer;
this.tracker = tracker;
- this.minificationEnabled = minificationEnabled;
- this.resourceChangeTracker = resourceChangeTracker;
- }
-
- @PostInjection
- public void listenToInvalidations(ResourceChangeTracker resourceChangeTracker)
- {
- resourceChangeTracker.clearOnInvalidation(uncompressedCache);
- resourceChangeTracker.clearOnInvalidation(compressedCache);
+ this.javaScriptStackAssembler = javaScriptStackAssembler;
}
public boolean handleAssetRequest(Request request, Response response, final String extraPath) throws IOException
@@ -115,114 +84,25 @@ public class StackAssetRequestHandler implements AssetRequestHandler
private StreamableResource getResource(String extraPath, boolean compressed) throws IOException
{
- return compressed ? getCompressedResource(extraPath) : getUncompressedResource(extraPath);
- }
-
- private synchronized StreamableResource getCompressedResource(String extraPath) throws IOException
- {
- StreamableResource result = compressedCache.get(extraPath);
-
- if (result == null)
- {
- StreamableResource uncompressed = getUncompressedResource(extraPath);
- result = compressStream(uncompressed);
- compressedCache.put(extraPath, result);
- }
-
- return result;
- }
-
- private synchronized StreamableResource getUncompressedResource(String extraPath) throws IOException
- {
- StreamableResource result = uncompressedCache.get(extraPath);
-
- if (result == null)
- {
- result = assembleStackContent(extraPath);
- uncompressedCache.put(extraPath, result);
- }
-
- return result;
- }
-
- private StreamableResource 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 StreamableResource assembleStackContent(String localeName, String stackName) throws IOException
- {
- localizationSetter.setNonPersistentLocaleFromLocaleName(localeName);
-
- JavaScriptStack stack = javascriptStackSource.getStack(stackName);
- List<Asset> libraries = stack.getJavaScriptLibraries();
-
- StreamableResource stackContent = assembleStackContent(localeName, stackName, libraries);
-
- return minificationEnabled ? resourceMinimizer.minimize(stackContent) : stackContent;
- }
-
- private StreamableResource assembleStackContent(String localeName, String stackName, List<Asset> libraries) throws IOException
- {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
- PrintWriter writer = new PrintWriter(osw, true);
- long lastModified = 0;
-
- StringBuilder description = new StringBuilder(String.format("'%s' JavaScript stack, for locale %s, resources=", stackName, localeName));
- String sep = "";
-
- JSONArray paths = new JSONArray();
-
- for (Asset library : libraries)
{
- String path = library.toClientURL();
-
- paths.put(path);
-
- writer.format("\n/* %s */;\n", path);
-
- Resource resource = library.getResource();
-
- description.append(sep).append(resource.toString());
- sep = ", ";
-
- StreamableResource streamable = streamableResourceSource.getStreamableResource(resource,
- StreamableResourceProcessing.FOR_AGGREGATION, resourceChangeTracker);
-
- streamable.streamTo(stream);
-
- lastModified = Math.max(lastModified, streamable.getLastModified());
+ throw new RuntimeException("Invalid path for a stack asset request.");
}
- writer.close();
+ // TODO: Extract the stack's aggregate checksum as well
- return new StreamableResourceImpl(
- description.toString(),
- JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSABLE, lastModified,
- new BytestreamCache(stream));
- }
+ String localeName = matcher.group(2);
+ String stackName = matcher.group(3);
- private StreamableResource compressStream(StreamableResource uncompressed) throws IOException
- {
- ByteArrayOutputStream compressed = new ByteArrayOutputStream();
- OutputStream compressor = new BufferedOutputStream(new GZIPOutputStream(compressed));
-
- uncompressed.streamTo(compressor);
+ // Yes, I have a big regret that the JavaScript stack stuff relies on this global, rather than
+ // having it passed around properly.
- compressor.close();
+ localizationSetter.setNonPersistentLocaleFromLocaleName(localeName);
- BytestreamCache cache = new BytestreamCache(compressed);
+ // TODO: Verify request checksum against actual
- return new StreamableResourceImpl(uncompressed.getDescription(), JAVASCRIPT_CONTENT_TYPE, CompressionStatus.COMPRESSED,
- uncompressed.getLastModified(), cache);
+ return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
}
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index 02b0822..3ff8c06 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -14,20 +14,23 @@
package org.apache.tapestry5.internal.services.javascript;
-import java.util.List;
-
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
-import org.apache.tapestry5.internal.services.RequestConstants;
+import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResource;
import org.apache.tapestry5.services.javascript.JavaScriptStack;
import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
+import java.io.IOException;
+import java.util.List;
+
public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathConstructor
{
private final ThreadLocale threadLocale;
@@ -36,6 +39,8 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
private final JavaScriptStackSource javascriptStackSource;
+ private final JavaScriptStackAssembler assembler;
+
private final boolean combineScripts;
private final Mapper<Asset, String> toPath = new Mapper<Asset, String>()
@@ -47,14 +52,15 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
};
public JavaScriptStackPathConstructorImpl(ThreadLocale threadLocale, AssetPathConstructor assetPathConstructor,
- JavaScriptStackSource javascriptStackSource,
-
- @Symbol(SymbolConstants.COMBINE_SCRIPTS)
- boolean combineScripts)
+ JavaScriptStackSource javascriptStackSource,
+ JavaScriptStackAssembler assembler,
+ @Symbol(SymbolConstants.COMBINE_SCRIPTS)
+ boolean combineScripts)
{
this.threadLocale = threadLocale;
this.assetPathConstructor = assetPathConstructor;
this.javascriptStackSource = javascriptStackSource;
+ this.assembler = assembler;
this.combineScripts = combineScripts;
}
@@ -65,7 +71,9 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
List<Asset> assets = stack.getJavaScriptLibraries();
if (assets.size() > 1 && combineScripts)
+ {
return combinedStackURL(stackName);
+ }
return toPaths(assets);
}
@@ -73,19 +81,27 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
private List<String> toPaths(List<Asset> assets)
{
assert assets != null;
+
return F.flow(assets).map(toPath).toList();
}
private List<String> combinedStackURL(String stackName)
{
- String path = String.format("%s/%s.js", threadLocale.getLocale().toString(), stackName);
+ try
+ {
+ StreamableResource streamable = assembler.assembleJavaScriptResourceForStack(stackName, false);
- // TODO: Come up with a virtual Resource that represents the actual combined contents. This may involve
- // looping through the StreamableResourceSource and wrapping the result as a VirtualResource.
+ String path = stackName + ".js";
- String stackURL = assetPathConstructor.constructAssetPath(RequestConstants.STACK_FOLDER, path, null);
+ String stackURL = assetPathConstructor.constructStackAssetPath(threadLocale.getLocale().toString(), path, streamable);
- return CollectionFactory.newList(stackURL);
+ return CollectionFactory.newList(stackURL);
+ } catch (IOException ex)
+ {
+ throw new RuntimeException(String.format("Unable to construct path for '%s' JavaScript stack: %s",
+ stackName,
+ InternalUtils.toMessage(ex)), ex);
+ }
}
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
index 1412a7b..17fb2b7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetPathConstructor.java
@@ -17,6 +17,8 @@ package org.apache.tapestry5.services.assets;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.annotations.IncompatibleChange;
+import java.io.IOException;
+
/**
* Encapsulates the logic or creating the path portion of an asset URL, including
* the application version.
@@ -39,6 +41,20 @@ public interface AssetPathConstructor
* @return path portion of asset URL, which is everything needed by the {@link org.apache.tapestry5.internal.services.AssetDispatcher}
* to find and stream the resource
*/
- @IncompatibleChange(release = "5.4", details = "resource parameter added")
- String constructAssetPath(String virtualFolder, String path, Resource resource);
+ @IncompatibleChange(release = "5.4", details = "resource parameter added, IOException may not be thrown")
+ String constructAssetPath(String virtualFolder, String path, Resource resource) throws IOException;
+
+ /**
+ * Constructs an asset path for a aggregated {@linkplain org.apache.tapestry5.services.javascript.JavaScriptStack stack}.
+ *
+ * @param localeName
+ * name of the locale
+ * @param path
+ * based on the name of the core stack
+ * @param resource
+ * the aggregated stack (used when generating the checksum)
+ * @return path that identifies the checksum, locale, and path
+ * @since 5.4
+ */
+ String constructStackAssetPath(String localeName, String path, StreamableResource resource) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
index d78e925..0955427 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/AssetsModule.java
@@ -48,6 +48,7 @@ public class AssetsModule
binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
binder.bind(ResourceMinimizer.class, MasterResourceMinimizer.class);
binder.bind(AssetChecksumGenerator.class, AssetChecksumGeneratorImpl.class);
+ binder.bind(JavaScriptStackAssembler.class, JavaScriptStackAssemblerImpl.class);
}
@Contribute(AssetSource.class)
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
index 1aca779..67a72d8 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/LibraryTests.groovy
@@ -1,8 +1,10 @@
package org.apache.tapestry5.integration.app1
import org.apache.tapestry5.integration.GroovyTapestryCoreTestCase
+import org.apache.tapestry5.test.TapestryTestConfiguration
import org.testng.annotations.Test
+@TapestryTestConfiguration(webAppFolder = "src/test/app1")
class LibraryTests extends GroovyTapestryCoreTestCase
{
@@ -17,7 +19,9 @@ class LibraryTests extends GroovyTapestryCoreTestCase
String assetURL = getAttribute("//img[@id='t5logo']/@src")
- assert assetURL.contains("lib/alpha/pages")
+ def pattern = ~"/assets/lib/alpha/\\w+/pages/tapestry\\.png"
+
+ assert pattern.matcher(assetURL).matches()
assertDownloadedAsset assetURL, "src/test/resources/org/apache/tapestry5/integration/locallib/alpha/pages/tapestry.png"
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/373f5cf5/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
index 2f19fe0..cb0fa33 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ContextAssetFactoryTest.java
@@ -22,6 +22,8 @@ import org.apache.tapestry5.services.Context;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
import org.testng.annotations.Test;
+import java.io.IOException;
+
public class ContextAssetFactoryTest extends InternalBaseTestCase
{
private final IdentityAssetPathConverter converter = new IdentityAssetPathConverter();
@@ -42,7 +44,7 @@ public class ContextAssetFactoryTest extends InternalBaseTestCase
}
@Test
- public void asset_client_URL()
+ public void asset_client_URL() throws IOException
{
Context context = mockContext();
AssetPathConstructor apc = newMock(AssetPathConstructor.class);