blob: f6b9156d86ba7113f7dea2126d1e0a262ebecd67 [file] [log] [blame]
// 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.modules;
import java.util.List;
import java.util.Map;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.internal.AssetConstants;
import org.apache.tapestry5.internal.InternalConstants;
import org.apache.tapestry5.internal.services.AssetSourceImpl;
import org.apache.tapestry5.internal.services.ClasspathAssetAliasManagerImpl;
import org.apache.tapestry5.internal.services.ClasspathAssetFactory;
import org.apache.tapestry5.internal.services.ContextAssetFactory;
import org.apache.tapestry5.internal.services.ExternalUrlAssetFactory;
import org.apache.tapestry5.internal.services.IdentityAssetPathConverter;
import org.apache.tapestry5.internal.services.RequestConstants;
import org.apache.tapestry5.internal.services.ResourceStreamer;
import org.apache.tapestry5.internal.services.assets.AssetChecksumGeneratorImpl;
import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
import org.apache.tapestry5.internal.services.assets.CSSURLRewriter;
import org.apache.tapestry5.internal.services.assets.ClasspathAssetRequestHandler;
import org.apache.tapestry5.internal.services.assets.CompressionAnalyzerImpl;
import org.apache.tapestry5.internal.services.assets.ContentTypeAnalyzerImpl;
import org.apache.tapestry5.internal.services.assets.ContextAssetRequestHandler;
import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssemblerImpl;
import org.apache.tapestry5.internal.services.assets.JavaScriptStackMinimizeDisabler;
import org.apache.tapestry5.internal.services.assets.MasterResourceMinimizer;
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.SRSMinimizingInterceptor;
import org.apache.tapestry5.internal.services.assets.StackAssetRequestHandler;
import org.apache.tapestry5.internal.services.assets.StreamableResourceSourceImpl;
import org.apache.tapestry5.internal.services.assets.UTF8ForTextAssets;
import org.apache.tapestry5.internal.services.messages.ClientLocalizationMessageResource;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OperationTracker;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Autobuild;
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.Primary;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ChainBuilder;
import org.apache.tapestry5.ioc.services.FactoryDefaults;
import org.apache.tapestry5.ioc.services.SymbolProvider;
import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.AssetFactory;
import org.apache.tapestry5.services.AssetPathConverter;
import org.apache.tapestry5.services.AssetRequestDispatcher;
import org.apache.tapestry5.services.AssetSource;
import org.apache.tapestry5.services.ClasspathAssetAliasManager;
import org.apache.tapestry5.services.ClasspathAssetProtectionRule;
import org.apache.tapestry5.services.ClasspathProvider;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ContextProvider;
import org.apache.tapestry5.services.Core;
import org.apache.tapestry5.services.Dispatcher;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
import org.apache.tapestry5.services.assets.AssetPathConstructor;
import org.apache.tapestry5.services.assets.AssetRequestHandler;
import org.apache.tapestry5.services.assets.CompressionAnalyzer;
import org.apache.tapestry5.services.assets.ContentTypeAnalyzer;
import org.apache.tapestry5.services.assets.ResourceMinimizer;
import org.apache.tapestry5.services.assets.StreamableResourceSource;
import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
import org.apache.tapestry5.services.messages.ComponentMessagesSource;
/**
* @since 5.3
*/
@Marker(Core.class)
public class AssetsModule
{
public static void bind(ServiceBinder binder)
{
binder.bind(AssetFactory.class, ClasspathAssetFactory.class).withSimpleId();
binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
binder.bind(AssetPathConstructor.class, AssetPathConstructorImpl.class);
binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
binder.bind(AssetSource.class, AssetSourceImpl.class);
binder.bind(StreamableResourceSource.class, StreamableResourceSourceImpl.class);
binder.bind(CompressionAnalyzer.class, CompressionAnalyzerImpl.class);
binder.bind(ContentTypeAnalyzer.class, ContentTypeAnalyzerImpl.class);
binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
binder.bind(ResourceMinimizer.class, MasterResourceMinimizer.class);
binder.bind(AssetChecksumGenerator.class, AssetChecksumGeneratorImpl.class);
binder.bind(JavaScriptStackAssembler.class, JavaScriptStackAssemblerImpl.class);
}
@Contribute(AssetSource.class)
public void configureStandardAssetFactories(MappedConfiguration<String, AssetFactory> configuration,
@ContextProvider
AssetFactory contextAssetFactory,
@ClasspathProvider
AssetFactory classpathAssetFactory)
{
configuration.add(AssetConstants.CONTEXT, contextAssetFactory);
configuration.add(AssetConstants.CLASSPATH, classpathAssetFactory);
configuration.add(AssetConstants.HTTP, new ExternalUrlAssetFactory(AssetConstants.HTTP));
configuration.add(AssetConstants.HTTPS, new ExternalUrlAssetFactory(AssetConstants.HTTPS));
configuration.add(AssetConstants.FTP, new ExternalUrlAssetFactory(AssetConstants.FTP));
configuration.add(AssetConstants.PROTOCOL_RELATIVE, new ExternalUrlAssetFactory(AssetConstants.PROTOCOL_RELATIVE));
}
@Contribute(SymbolProvider.class)
@FactoryDefaults
public static void setupSymbols(MappedConfiguration<String, Object> configuration)
{
// Minification may be enabled in production mode, but unless a minimizer is provided, nothing
// will change.
configuration.add(SymbolConstants.MINIFICATION_ENABLED, SymbolConstants.PRODUCTION_MODE_VALUE);
configuration.add(SymbolConstants.GZIP_COMPRESSION_ENABLED, true);
configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "${tapestry.asset.root}/bootstrap");
configuration.add("tapestry.asset.root", "classpath:META-INF/assets/tapestry5");
configuration.add(SymbolConstants.OMIT_EXPIRATION_CACHE_CONTROL_HEADER, "max-age=60,must-revalidate");
}
// 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,
AssetChecksumGenerator checksumGenerator)
{
return gzipEnabled
? new SRSCompressingInterceptor(delegate, compressionCutoff, checksumGenerator)
: 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(delegate, tracker)
: null;
}
@Decorate(id = "Cache", serviceInterface = StreamableResourceSource.class)
@Order("after:GZipCompression")
public StreamableResourceSource enableUncompressedCaching(StreamableResourceSource delegate,
ResourceChangeTracker tracker)
{
return new SRSCachingInterceptor(delegate, tracker);
}
// Goes after cache, to ensure that what we are caching is the minified version.
@Decorate(id = "Minification", serviceInterface = StreamableResourceSource.class)
@Order("after:Cache,TextUTF8")
public StreamableResourceSource enableMinification(StreamableResourceSource delegate, ResourceMinimizer minimizer,
@Symbol(SymbolConstants.MINIFICATION_ENABLED)
boolean enabled)
{
return enabled
? new SRSMinimizingInterceptor(delegate, minimizer)
: null;
}
// Ordering this after minification means that the URL replacement happens first;
// then the minification, then the uncompressed caching, then compression, then compressed
// cache.
@Decorate(id = "CSSURLRewrite", serviceInterface = StreamableResourceSource.class)
@Order("after:Minification")
public StreamableResourceSource enableCSSURLRewriting(StreamableResourceSource delegate,
OperationTracker tracker,
AssetSource assetSource,
AssetChecksumGenerator checksumGenerator,
@Symbol(SymbolConstants.STRICT_CSS_URL_REWRITING) boolean strictCssUrlRewriting)
{
return new CSSURLRewriter(delegate, tracker, assetSource, checksumGenerator, strictCssUrlRewriting);
}
@Decorate(id = "DisableMinificationForStacks", serviceInterface = StreamableResourceSource.class)
@Order("before:Minification")
public StreamableResourceSource setupDisableMinificationByJavaScriptStack(StreamableResourceSource delegate,
@Symbol(SymbolConstants.MINIFICATION_ENABLED)
boolean enabled,
JavaScriptStackSource javaScriptStackSource,
Request request)
{
return enabled
? new JavaScriptStackMinimizeDisabler(delegate, javaScriptStackSource, request)
: null;
}
/**
* Ensures that all "text/*" assets are given the UTF-8 charset.
*
* @since 5.4
*/
@Decorate(id = "TextUTF8", serviceInterface = StreamableResourceSource.class)
@Order("after:Cache")
public StreamableResourceSource setupTextAssetsAsUTF8(StreamableResourceSource delegate)
{
return new UTF8ForTextAssets(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>svg</dt>
* <dd>image/svg+xml</dd>
* <dt>swf</dt>
* <dd>application/x-shockwave-flash</dd>
* <dt>woff</dt>
* <dd>application/font-woff</dd>
* <dt>tff</dt> <dd>application/x-font-ttf</dd>
* <dt>eot</dt> <dd>application/vnd.ms-fontobject</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");
configuration.add("svg", "image/svg+xml");
configuration.add("woff", "application/font-woff");
configuration.add("ttf", "application/x-font-ttf");
configuration.add("eot", "application/vnd.ms-fontobject");
}
/**
* Disables compression for the following content types:
* <ul>
* <li>image/jpeg</li>
* <li>image/gif</li>
* <li>image/png</li>
* <li>image/svg+xml</li>
* <li>application/x-shockwave-flash</li>
* <li>application/font-woff</li>
* <li>application/x-font-ttf</li>
* <li>application/vnd.ms-fontobject</li>
* </ul>
*/
@Contribute(CompressionAnalyzer.class)
public void disableCompressionForImageTypes(MappedConfiguration<String, Boolean> configuration)
{
configuration.add("image/*", false);
configuration.add("image/svg+xml", true);
configuration.add("application/x-shockwave-flash", false);
configuration.add("application/font-woff", false);
configuration.add("application/x-font-ttf", false);
configuration.add("application/vnd.ms-fontobject", false);
}
@Marker(ContextProvider.class)
public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
AssetPathConstructor assetPathConstructor,
ResponseCompressionAnalyzer compressionAnalyzer,
ResourceChangeTracker resourceChangeTracker,
StreamableResourceSource streamableResourceSource)
{
return new ContextAssetFactory(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor, globals.getContext());
}
@Contribute(ClasspathAssetAliasManager.class)
public static void addApplicationAndTapestryMappings(MappedConfiguration<String, String> configuration,
@Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
String appPackage)
{
configuration.add("tapestry", "org/apache/tapestry5");
configuration.add("app", toPackagePath(appPackage));
}
/**
* Contributes an handler for each mapped classpath alias, as well handlers for context assets
* and stack assets (combined {@link org.apache.tapestry5.services.javascript.JavaScriptStack} files).
*/
@Contribute(Dispatcher.class)
@AssetRequestDispatcher
public static void provideBuiltinAssetDispatchers(MappedConfiguration<String, AssetRequestHandler> configuration,
@ContextProvider
AssetFactory contextAssetFactory,
@Autobuild
StackAssetRequestHandler stackAssetRequestHandler,
ClasspathAssetAliasManager classpathAssetAliasManager,
ResourceStreamer streamer,
AssetSource assetSource,
ClasspathAssetProtectionRule classpathAssetProtectionRule)
{
Map<String, String> mappings = classpathAssetAliasManager.getMappings();
for (String folder : mappings.keySet())
{
String path = mappings.get(folder);
configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetSource, path, classpathAssetProtectionRule));
}
configuration.add(RequestConstants.CONTEXT_FOLDER,
new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
}
@Contribute(ClasspathAssetAliasManager.class)
public static void addMappingsForLibraryVirtualFolders(MappedConfiguration<String, String> configuration,
ComponentClassResolver resolver)
{
// Each library gets a mapping or its folder automatically
Map<String, String> folderToPackageMapping = resolver.getFolderToPackageMapping();
for (String folder : folderToPackageMapping.keySet())
{
// This is the 5.3 version, which is still supported:
configuration.add(folder, toPackagePath(folderToPackageMapping.get(folder)));
// This is the 5.4 version; once 5.3 support is dropped, this can be simplified, and the
// "meta/" prefix stripped out.
String folderSuffix = folder.equals("") ? folder : "/" + folder;
configuration.add("meta" + folderSuffix, "META-INF/assets" + folderSuffix);
}
}
private static String toPackagePath(String packageName)
{
return packageName.replace('.', '/');
}
/**
* Contributes:
* <dl>
* <dt>ClientLocalization</dt>
* <dd>A virtual resource of formatting symbols for decimal numbers</dd>
* <dt>Core</dt>
* <dd>Built in messages used by Tapestry's default validators and components</dd>
* <dt>AppCatalog</dt>
* <dd>The Resource defined by {@link SymbolConstants#APPLICATION_CATALOG}</dd>
* <dt>
* </dl>
*
* @since 5.2.0
*/
@Contribute(ComponentMessagesSource.class)
public static void setupGlobalMessageCatalog(AssetSource assetSource,
@Symbol(SymbolConstants.APPLICATION_CATALOG)
Resource applicationCatalog, OrderedConfiguration<Resource> configuration)
{
configuration.add("ClientLocalization", new ClientLocalizationMessageResource());
configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
configuration.add("AppCatalog", applicationCatalog);
}
@Contribute(Dispatcher.class)
@Primary
public static void setupAssetDispatch(OrderedConfiguration<Dispatcher> configuration,
@AssetRequestDispatcher
Dispatcher assetDispatcher)
{
// This goes first because an asset to be streamed may have an file
// extension, such as
// ".html", that will confuse the later dispatchers.
configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
}
@Primary
public static ClasspathAssetProtectionRule buildClasspathAssetProtectionRule(
List<ClasspathAssetProtectionRule> rules, ChainBuilder chainBuilder)
{
return chainBuilder.build(ClasspathAssetProtectionRule.class, rules);
}
final private static class LowercaseSuffixClasspathAssetProtectinRule implements ClasspathAssetProtectionRule {
final private String suffix;
public LowercaseSuffixClasspathAssetProtectinRule(String suffix) {
this.suffix = suffix;
}
@Override
public boolean block(String path) {
return path.toLowerCase().endsWith(suffix);
}
}
public static void contributeClasspathAssetProtectionRule(
OrderedConfiguration<ClasspathAssetProtectionRule> configuration)
{
ClasspathAssetProtectionRule classFileRule = new LowercaseSuffixClasspathAssetProtectinRule(".class");
configuration.add("ClassFile", classFileRule);
ClasspathAssetProtectionRule propertiesFileRule = new LowercaseSuffixClasspathAssetProtectinRule(".properties");
configuration.add("PropertiesFile", propertiesFileRule);
ClasspathAssetProtectionRule xmlFileRule = new LowercaseSuffixClasspathAssetProtectinRule(".xml");
configuration.add("XMLFile", xmlFileRule);
}
}