001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.modules;
014
015import org.apache.tapestry5.SymbolConstants;
016import org.apache.tapestry5.internal.AssetConstants;
017import org.apache.tapestry5.internal.InternalConstants;
018import org.apache.tapestry5.internal.services.*;
019import org.apache.tapestry5.internal.services.assets.*;
020import org.apache.tapestry5.internal.services.messages.ClientLocalizationMessageResource;
021import org.apache.tapestry5.ioc.*;
022import org.apache.tapestry5.ioc.annotations.*;
023import org.apache.tapestry5.ioc.services.FactoryDefaults;
024import org.apache.tapestry5.ioc.services.SymbolProvider;
025import org.apache.tapestry5.services.*;
026import org.apache.tapestry5.services.assets.*;
027import org.apache.tapestry5.services.messages.ComponentMessagesSource;
028
029import java.util.Map;
030
031/**
032 * @since 5.3
033 */
034@Marker(Core.class)
035public class AssetsModule
036{
037    public static void bind(ServiceBinder binder)
038    {
039        binder.bind(AssetFactory.class, ClasspathAssetFactory.class).withSimpleId();
040        binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
041        binder.bind(AssetPathConstructor.class, AssetPathConstructorImpl.class);
042        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
043        binder.bind(AssetSource.class, AssetSourceImpl.class);
044        binder.bind(StreamableResourceSource.class, StreamableResourceSourceImpl.class);
045        binder.bind(CompressionAnalyzer.class, CompressionAnalyzerImpl.class);
046        binder.bind(ContentTypeAnalyzer.class, ContentTypeAnalyzerImpl.class);
047        binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
048        binder.bind(ResourceMinimizer.class, MasterResourceMinimizer.class);
049        binder.bind(AssetChecksumGenerator.class, AssetChecksumGeneratorImpl.class);
050        binder.bind(JavaScriptStackAssembler.class, JavaScriptStackAssemblerImpl.class);
051    }
052
053    @Contribute(AssetSource.class)
054    public void configureStandardAssetFactories(MappedConfiguration<String, AssetFactory> configuration,
055                                                @ContextProvider
056                                                AssetFactory contextAssetFactory,
057
058                                                @ClasspathProvider
059                                                AssetFactory classpathAssetFactory)
060    {
061        configuration.add(AssetConstants.CONTEXT, contextAssetFactory);
062        configuration.add(AssetConstants.CLASSPATH, classpathAssetFactory);
063    }
064
065
066    @Contribute(SymbolProvider.class)
067    @FactoryDefaults
068    public static void setupSymbols(MappedConfiguration<String, Object> configuration)
069    {
070        // Minification may be enabled in production mode, but unless a minimizer is provided, nothing
071        // will change.
072        configuration.add(SymbolConstants.MINIFICATION_ENABLED, SymbolConstants.PRODUCTION_MODE_VALUE);
073        configuration.add(SymbolConstants.GZIP_COMPRESSION_ENABLED, true);
074        configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
075        configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
076
077        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
078
079        configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "${tapestry.asset.root}/bootstrap");
080
081        configuration.add("tapestry.asset.root", "classpath:META-INF/assets/tapestry5");
082    }
083
084    // The use of decorators is to allow third-parties to get their own extensions
085    // into the pipeline.
086
087    @Decorate(id = "GZipCompression", serviceInterface = StreamableResourceSource.class)
088    public StreamableResourceSource enableCompression(StreamableResourceSource delegate,
089                                                      @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
090                                                      boolean gzipEnabled, @Symbol(SymbolConstants.MIN_GZIP_SIZE)
091                                                      int compressionCutoff,
092                                                      AssetChecksumGenerator checksumGenerator)
093    {
094        return gzipEnabled
095                ? new SRSCompressingInterceptor(delegate, compressionCutoff, checksumGenerator)
096                : null;
097    }
098
099    @Decorate(id = "CacheCompressed", serviceInterface = StreamableResourceSource.class)
100    @Order("before:GZIpCompression")
101    public StreamableResourceSource enableCompressedCaching(StreamableResourceSource delegate,
102                                                            @Symbol(SymbolConstants.GZIP_COMPRESSION_ENABLED)
103                                                            boolean gzipEnabled, ResourceChangeTracker tracker)
104    {
105        return gzipEnabled
106                ? new SRSCompressedCachingInterceptor(delegate, tracker)
107                : null;
108    }
109
110    @Decorate(id = "Cache", serviceInterface = StreamableResourceSource.class)
111    @Order("after:GZipCompression")
112    public StreamableResourceSource enableUncompressedCaching(StreamableResourceSource delegate,
113                                                              ResourceChangeTracker tracker)
114    {
115        return new SRSCachingInterceptor(delegate, tracker);
116    }
117
118    // Goes after cache, to ensure that what we are caching is the minified version.
119    @Decorate(id = "Minification", serviceInterface = StreamableResourceSource.class)
120    @Order("after:Cache")
121    public StreamableResourceSource enableMinification(StreamableResourceSource delegate, ResourceMinimizer minimizer,
122                                                       @Symbol(SymbolConstants.MINIFICATION_ENABLED)
123                                                       boolean enabled)
124    {
125        return enabled
126                ? new SRSMinimizingInterceptor(delegate, minimizer)
127                : null;
128    }
129
130    // Ordering this after minification means that the URL replacement happens first;
131    // then the minification, then the uncompressed caching, then compression, then compressed
132    // cache.
133    @Decorate(id = "CSSURLRewrite", serviceInterface = StreamableResourceSource.class)
134    @Order("after:Minification")
135    public StreamableResourceSource enableCSSURLRewriting(StreamableResourceSource delegate,
136                                                          OperationTracker tracker,
137                                                          AssetSource assetSource,
138                                                          AssetChecksumGenerator checksumGenerator)
139    {
140        return new CSSURLRewriter(delegate, tracker, assetSource, checksumGenerator);
141    }
142
143    /**
144     * Adds content types:
145     * <dl>
146     * <dt>css</dt>
147     * <dd>text/css</dd>
148     * <dt>js</dt>
149     * <dd>text/javascript</dd>
150     * <dt>jpg, jpeg</dt>
151     * <dd>image/jpeg</dd>
152     * <dt>gif</dt>
153     * <dd>image/gif</dd>
154     * <dt>png</dt>
155     * <dd>image/png</dd>
156     * <dt>svg</dt>
157     * <dd>image/svg+xml</dd>
158     * <dt>swf</dt>
159     * <dd>application/x-shockwave-flash</dd>
160     * <dt>woff</dt>
161     * <dd>application/font-woff</dd>
162     * <dt>tff</dt> <dd>application/x-font-ttf</dd>
163     * <dt>eot</dt> <dd>application/vnd.ms-fontobject</dd>
164     * </dl>
165     */
166    @Contribute(ContentTypeAnalyzer.class)
167    public void setupDefaultContentTypeMappings(MappedConfiguration<String, String> configuration)
168    {
169        configuration.add("css", "text/css");
170        configuration.add("js", "text/javascript");
171        configuration.add("gif", "image/gif");
172        configuration.add("jpg", "image/jpeg");
173        configuration.add("jpeg", "image/jpeg");
174        configuration.add("png", "image/png");
175        configuration.add("swf", "application/x-shockwave-flash");
176        configuration.add("svg", "image/svg+xml");
177        configuration.add("woff", "application/font-woff");
178        configuration.add("ttf", "application/x-font-ttf");
179        configuration.add("eot", "application/vnd.ms-fontobject");
180    }
181
182    /**
183     * Disables compression for the following content types:
184     * <ul>
185     * <li>image/jpeg</li>
186     * <li>image/gif</li>
187     * <li>image/png</li>
188     * <li>application/x-shockwave-flash</li>
189     * <li>application/font-woff</li>
190     * <li>application/x-font-ttf</li>
191     * <li>application/vnd.ms-fontobject</li>
192     * </ul>
193     */
194    @Contribute(CompressionAnalyzer.class)
195    public void disableCompressionForImageTypes(MappedConfiguration<String, Boolean> configuration)
196    {
197        configuration.add("image/*", false);
198        configuration.add("application/x-shockwave-flash", false);
199        configuration.add("application/font-woff", false);
200        configuration.add("application/x-font-ttf", false);
201        configuration.add("application/vnd.ms-fontobject", false);
202    }
203
204    @Marker(ContextProvider.class)
205    public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
206                                                        AssetPathConstructor assetPathConstructor,
207                                                        ResponseCompressionAnalyzer compressionAnalyzer,
208                                                        ResourceChangeTracker resourceChangeTracker,
209                                                        StreamableResourceSource streamableResourceSource)
210    {
211        return new ContextAssetFactory(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor, globals.getContext());
212    }
213
214    @Contribute(ClasspathAssetAliasManager.class)
215    public static void addApplicationAndTapestryMappings(MappedConfiguration<String, String> configuration,
216
217                                                         @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
218                                                         String appPackage)
219    {
220        configuration.add("tapestry", "org/apache/tapestry5");
221
222        configuration.add("app", toPackagePath(appPackage));
223    }
224
225    /**
226     * Contributes an handler for each mapped classpath alias, as well handlers for context assets
227     * and stack assets (combined {@link org.apache.tapestry5.services.javascript.JavaScriptStack} files).
228     */
229    @Contribute(Dispatcher.class)
230    @AssetRequestDispatcher
231    public static void provideBuiltinAssetDispatchers(MappedConfiguration<String, AssetRequestHandler> configuration,
232
233                                                      @ContextProvider
234                                                      AssetFactory contextAssetFactory,
235
236                                                      @Autobuild
237                                                      StackAssetRequestHandler stackAssetRequestHandler,
238
239                                                      ClasspathAssetAliasManager classpathAssetAliasManager,
240                                                      ResourceStreamer streamer,
241                                                      AssetSource assetSource)
242    {
243        Map<String, String> mappings = classpathAssetAliasManager.getMappings();
244
245        for (String folder : mappings.keySet())
246        {
247            String path = mappings.get(folder);
248
249            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetSource, path));
250        }
251
252        configuration.add(RequestConstants.CONTEXT_FOLDER,
253                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource()));
254
255        configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
256
257    }
258
259    @Contribute(ClasspathAssetAliasManager.class)
260    public static void addMappingsForLibraryVirtualFolders(MappedConfiguration<String, String> configuration,
261                                                           ComponentClassResolver resolver)
262    {
263        // Each library gets a mapping or its folder automatically
264
265        Map<String, String> folderToPackageMapping = resolver.getFolderToPackageMapping();
266
267        for (String folder : folderToPackageMapping.keySet())
268        {
269            // This is the 5.3 version, which is still supported:
270            configuration.add(folder, toPackagePath(folderToPackageMapping.get(folder)));
271
272            // This is the 5.4 version; once 5.3 support is dropped, this can be simplified, and the
273            // "meta/" prefix stripped out.
274            String folderSuffix = folder.equals("") ? folder : "/" + folder;
275
276            configuration.add("meta" + folderSuffix, "META-INF/assets" + folderSuffix);
277        }
278    }
279
280    private static String toPackagePath(String packageName)
281    {
282        return packageName.replace('.', '/');
283    }
284
285    /**
286     * Contributes:
287     * <dl>
288     * <dt>ClientLocalization</dt>
289     * <dd>A virtual resource of formatting symbols for decimal numbers</dd>
290     * <dt>Core</dt>
291     * <dd>Built in messages used by Tapestry's default validators and components</dd>
292     * <dt>AppCatalog</dt>
293     * <dd>The Resource defined by {@link SymbolConstants#APPLICATION_CATALOG}</dd>
294     * <dt>
295     *
296     * @since 5.2.0
297     */
298    @Contribute(ComponentMessagesSource.class)
299    public static void setupGlobalMessageCatalog(AssetSource assetSource,
300                                                 @Symbol(SymbolConstants.APPLICATION_CATALOG)
301                                                 Resource applicationCatalog, OrderedConfiguration<Resource> configuration)
302    {
303        configuration.add("ClientLocalization", new ClientLocalizationMessageResource());
304        configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
305        configuration.add("AppCatalog", applicationCatalog);
306    }
307
308    @Contribute(Dispatcher.class)
309    @Primary
310    public static void setupAssetDispatch(OrderedConfiguration<Dispatcher> configuration,
311                                          @AssetRequestDispatcher
312                                          Dispatcher assetDispatcher)
313    {
314
315        // This goes first because an asset to be streamed may have an file
316        // extension, such as
317        // ".html", that will confuse the later dispatchers.
318
319        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
320    }
321}