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 java.util.Collections;
016import java.util.LinkedHashMap;
017import java.util.List;
018import java.util.Map;
019
020import org.apache.tapestry5.SymbolConstants;
021import org.apache.tapestry5.commons.MappedConfiguration;
022import org.apache.tapestry5.commons.OrderedConfiguration;
023import org.apache.tapestry5.commons.Resource;
024import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
025import org.apache.tapestry5.http.internal.TapestryHttpInternalConstants;
026import org.apache.tapestry5.http.services.ApplicationGlobals;
027import org.apache.tapestry5.http.services.CompressionAnalyzer;
028import org.apache.tapestry5.http.services.Dispatcher;
029import org.apache.tapestry5.http.services.Request;
030import org.apache.tapestry5.http.services.ResponseCompressionAnalyzer;
031import org.apache.tapestry5.internal.AssetConstants;
032import org.apache.tapestry5.internal.services.AssetSourceImpl;
033import org.apache.tapestry5.internal.services.ClasspathAssetAliasManagerImpl;
034import org.apache.tapestry5.internal.services.ClasspathAssetFactory;
035import org.apache.tapestry5.internal.services.ContextAssetFactory;
036import org.apache.tapestry5.internal.services.ExternalUrlAssetFactory;
037import org.apache.tapestry5.internal.services.IdentityAssetPathConverter;
038import org.apache.tapestry5.internal.services.RequestConstants;
039import org.apache.tapestry5.internal.services.ResourceStreamer;
040import org.apache.tapestry5.internal.services.assets.AssetChecksumGeneratorImpl;
041import org.apache.tapestry5.internal.services.assets.AssetPathConstructorImpl;
042import org.apache.tapestry5.internal.services.assets.CSSURLRewriter;
043import org.apache.tapestry5.internal.services.assets.ClasspathAssetRequestHandler;
044import org.apache.tapestry5.internal.services.assets.CompressionAnalyzerImpl;
045import org.apache.tapestry5.internal.services.assets.ContentTypeAnalyzerImpl;
046import org.apache.tapestry5.internal.services.assets.ContextAssetRequestHandler;
047import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssembler;
048import org.apache.tapestry5.internal.services.assets.JavaScriptStackAssemblerImpl;
049import org.apache.tapestry5.internal.services.assets.JavaScriptStackMinimizeDisabler;
050import org.apache.tapestry5.internal.services.assets.MasterResourceMinimizer;
051import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
052import org.apache.tapestry5.internal.services.assets.ResourceChangeTrackerImpl;
053import org.apache.tapestry5.internal.services.assets.SRSCachingInterceptor;
054import org.apache.tapestry5.internal.services.assets.SRSCompressedCachingInterceptor;
055import org.apache.tapestry5.internal.services.assets.SRSCompressingInterceptor;
056import org.apache.tapestry5.internal.services.assets.SRSMinimizingInterceptor;
057import org.apache.tapestry5.internal.services.assets.StackAssetRequestHandler;
058import org.apache.tapestry5.internal.services.assets.StreamableResourceSourceImpl;
059import org.apache.tapestry5.internal.services.assets.UTF8ForTextAssets;
060import org.apache.tapestry5.internal.services.messages.ClientLocalizationMessageResource;
061import org.apache.tapestry5.ioc.OperationTracker;
062import org.apache.tapestry5.ioc.ServiceBinder;
063import org.apache.tapestry5.ioc.annotations.Autobuild;
064import org.apache.tapestry5.ioc.annotations.Contribute;
065import org.apache.tapestry5.ioc.annotations.Decorate;
066import org.apache.tapestry5.ioc.annotations.Marker;
067import org.apache.tapestry5.ioc.annotations.Order;
068import org.apache.tapestry5.ioc.annotations.Primary;
069import org.apache.tapestry5.ioc.annotations.Symbol;
070import org.apache.tapestry5.ioc.services.ChainBuilder;
071import org.apache.tapestry5.ioc.services.FactoryDefaults;
072import org.apache.tapestry5.ioc.services.SymbolProvider;
073import org.apache.tapestry5.services.AssetFactory;
074import org.apache.tapestry5.services.AssetPathConverter;
075import org.apache.tapestry5.services.AssetRequestDispatcher;
076import org.apache.tapestry5.services.AssetSource;
077import org.apache.tapestry5.services.ClasspathAssetAliasManager;
078import org.apache.tapestry5.services.ClasspathAssetProtectionRule;
079import org.apache.tapestry5.services.ClasspathProvider;
080import org.apache.tapestry5.services.ComponentClassResolver;
081import org.apache.tapestry5.services.ContextAssetProtectionRule;
082import org.apache.tapestry5.services.ContextProvider;
083import org.apache.tapestry5.services.Core;
084import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
085import org.apache.tapestry5.services.assets.AssetPathConstructor;
086import org.apache.tapestry5.services.assets.AssetRequestHandler;
087import org.apache.tapestry5.services.assets.ContentTypeAnalyzer;
088import org.apache.tapestry5.services.assets.ResourceMinimizer;
089import org.apache.tapestry5.services.assets.StreamableResourceSource;
090import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
091import org.apache.tapestry5.services.messages.ComponentMessagesSource;
092
093/**
094 * @since 5.3
095 */
096@Marker(Core.class)
097public class AssetsModule
098{
099    public static void bind(ServiceBinder binder)
100    {
101        binder.bind(AssetFactory.class, ClasspathAssetFactory.class).withSimpleId();
102        binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
103        binder.bind(AssetPathConstructor.class, AssetPathConstructorImpl.class);
104        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
105        binder.bind(AssetSource.class, AssetSourceImpl.class);
106        binder.bind(StreamableResourceSource.class, StreamableResourceSourceImpl.class);
107        binder.bind(CompressionAnalyzer.class, CompressionAnalyzerImpl.class);
108        binder.bind(ContentTypeAnalyzer.class, ContentTypeAnalyzerImpl.class);
109        binder.bind(ResourceChangeTracker.class, ResourceChangeTrackerImpl.class);
110        binder.bind(ResourceMinimizer.class, MasterResourceMinimizer.class);
111        binder.bind(AssetChecksumGenerator.class, AssetChecksumGeneratorImpl.class);
112        binder.bind(JavaScriptStackAssembler.class, JavaScriptStackAssemblerImpl.class);
113    }
114
115    @Contribute(AssetSource.class)
116    public void configureStandardAssetFactories(MappedConfiguration<String, AssetFactory> configuration,
117                                                @ContextProvider
118                                                AssetFactory contextAssetFactory,
119
120                                                @ClasspathProvider
121                                                AssetFactory classpathAssetFactory)
122    {
123        configuration.add(AssetConstants.CONTEXT, contextAssetFactory);
124        configuration.add(AssetConstants.CLASSPATH, classpathAssetFactory);
125        configuration.add(AssetConstants.HTTP, new ExternalUrlAssetFactory(AssetConstants.HTTP));
126        configuration.add(AssetConstants.HTTPS, new ExternalUrlAssetFactory(AssetConstants.HTTPS));
127        configuration.add(AssetConstants.FTP, new ExternalUrlAssetFactory(AssetConstants.FTP));
128        configuration.add(AssetConstants.PROTOCOL_RELATIVE, new ExternalUrlAssetFactory(AssetConstants.PROTOCOL_RELATIVE));
129    }
130
131
132    @Contribute(SymbolProvider.class)
133    @FactoryDefaults
134    public static void setupSymbols(MappedConfiguration<String, Object> configuration)
135    {
136        // Minification may be enabled in production mode, but unless a minimizer is provided, nothing
137        // will change.
138        configuration.add(SymbolConstants.MINIFICATION_ENABLED, SymbolConstants.PRODUCTION_MODE_VALUE);
139        configuration.add(SymbolConstants.COMBINE_SCRIPTS, SymbolConstants.PRODUCTION_MODE_VALUE);
140        configuration.add(SymbolConstants.ASSET_URL_FULL_QUALIFIED, false);
141
142        configuration.add(SymbolConstants.ASSET_PATH_PREFIX, "assets");
143
144        configuration.add(SymbolConstants.BOOTSTRAP_ROOT, "${tapestry.asset.root}/bootstrap");
145        configuration.add(SymbolConstants.FONT_AWESOME_ROOT, "${tapestry.asset.root}/font_awesome");
146
147        configuration.add("tapestry.asset.root", "classpath:META-INF/assets/tapestry5");
148        configuration.add(SymbolConstants.OMIT_EXPIRATION_CACHE_CONTROL_HEADER, "max-age=60,must-revalidate");
149    }
150
151    // The use of decorators is to allow third-parties to get their own extensions
152    // into the pipeline.
153
154    @Decorate(id = "GZipCompression", serviceInterface = StreamableResourceSource.class)
155    public StreamableResourceSource enableCompression(StreamableResourceSource delegate,
156                                                      @Symbol(TapestryHttpSymbolConstants.GZIP_COMPRESSION_ENABLED)
157                                                      boolean gzipEnabled, @Symbol(TapestryHttpSymbolConstants.MIN_GZIP_SIZE)
158                                                      int compressionCutoff,
159                                                      AssetChecksumGenerator checksumGenerator)
160    {
161        return gzipEnabled
162                ? new SRSCompressingInterceptor(delegate, compressionCutoff, checksumGenerator)
163                : null;
164    }
165
166    @Decorate(id = "CacheCompressed", serviceInterface = StreamableResourceSource.class)
167    @Order("before:GZIpCompression")
168    public StreamableResourceSource enableCompressedCaching(StreamableResourceSource delegate,
169                                                            @Symbol(TapestryHttpSymbolConstants.GZIP_COMPRESSION_ENABLED)
170                                                            boolean gzipEnabled, ResourceChangeTracker tracker)
171    {
172        return gzipEnabled
173                ? new SRSCompressedCachingInterceptor(delegate, tracker)
174                : null;
175    }
176
177    @Decorate(id = "Cache", serviceInterface = StreamableResourceSource.class)
178    @Order("after:GZipCompression")
179    public StreamableResourceSource enableUncompressedCaching(StreamableResourceSource delegate,
180                                                              ResourceChangeTracker tracker)
181    {
182        return new SRSCachingInterceptor(delegate, tracker);
183    }
184
185    // Goes after cache, to ensure that what we are caching is the minified version.
186    @Decorate(id = "Minification", serviceInterface = StreamableResourceSource.class)
187    @Order("after:Cache,TextUTF8")
188    public StreamableResourceSource enableMinification(StreamableResourceSource delegate, ResourceMinimizer minimizer,
189                                                       @Symbol(SymbolConstants.MINIFICATION_ENABLED)
190                                                       boolean enabled)
191    {
192        return enabled
193                ? new SRSMinimizingInterceptor(delegate, minimizer)
194                : null;
195    }
196
197    // Ordering this after minification means that the URL replacement happens first;
198    // then the minification, then the uncompressed caching, then compression, then compressed
199    // cache.
200    @Decorate(id = "CSSURLRewrite", serviceInterface = StreamableResourceSource.class)
201    @Order("after:Minification")
202    public StreamableResourceSource enableCSSURLRewriting(StreamableResourceSource delegate,
203                                                          OperationTracker tracker,
204                                                          AssetSource assetSource,
205                                                          AssetChecksumGenerator checksumGenerator,
206                                                          @Symbol(SymbolConstants.STRICT_CSS_URL_REWRITING) boolean strictCssUrlRewriting)
207    {
208        return new CSSURLRewriter(delegate, tracker, assetSource, checksumGenerator, strictCssUrlRewriting);
209    }
210
211    @Decorate(id = "DisableMinificationForStacks", serviceInterface = StreamableResourceSource.class)
212    @Order("before:Minification")
213    public StreamableResourceSource setupDisableMinificationByJavaScriptStack(StreamableResourceSource delegate,
214                                                                              @Symbol(SymbolConstants.MINIFICATION_ENABLED)
215                                                                              boolean enabled,
216                                                                              JavaScriptStackSource javaScriptStackSource,
217                                                                              Request request)
218    {
219        return enabled
220                ? new JavaScriptStackMinimizeDisabler(delegate, javaScriptStackSource, request)
221                : null;
222    }
223
224    /**
225     * Ensures that all "text/*" assets are given the UTF-8 charset.
226     *
227     * @since 5.4
228     */
229    @Decorate(id = "TextUTF8", serviceInterface = StreamableResourceSource.class)
230    @Order("after:Cache")
231    public StreamableResourceSource setupTextAssetsAsUTF8(StreamableResourceSource delegate)
232    {
233        return new UTF8ForTextAssets(delegate);
234    }
235
236    /**
237     * Adds content types:
238     * <dl>
239     * <dt>css</dt>
240     * <dd>text/css</dd>
241     * <dt>js</dt>
242     * <dd>text/javascript</dd>
243     * <dt>jpg, jpeg</dt>
244     * <dd>image/jpeg</dd>
245     * <dt>gif</dt>
246     * <dd>image/gif</dd>
247     * <dt>png</dt>
248     * <dd>image/png</dd>
249     * <dt>svg</dt>
250     * <dd>image/svg+xml</dd>
251     * <dt>swf</dt>
252     * <dd>application/x-shockwave-flash</dd>
253     * <dt>woff</dt>
254     * <dd>application/font-woff</dd>
255     * <dt>tff</dt> <dd>application/x-font-ttf</dd>
256     * <dt>eot</dt> <dd>application/vnd.ms-fontobject</dd>
257     * </dl>
258     */
259    @Contribute(ContentTypeAnalyzer.class)
260    public void setupDefaultContentTypeMappings(MappedConfiguration<String, String> configuration)
261    {
262        configuration.add("css", "text/css");
263        configuration.add("js", "text/javascript");
264        configuration.add("gif", "image/gif");
265        configuration.add("jpg", "image/jpeg");
266        configuration.add("jpeg", "image/jpeg");
267        configuration.add("png", "image/png");
268        configuration.add("swf", "application/x-shockwave-flash");
269        configuration.add("svg", "image/svg+xml");
270        configuration.add("woff", "application/font-woff");
271        configuration.add("ttf", "application/x-font-ttf");
272        configuration.add("eot", "application/vnd.ms-fontobject");
273    }
274
275    /**
276     * Disables compression for the following content types:
277     * <ul>
278     * <li>image/jpeg</li>
279     * <li>image/gif</li>
280     * <li>image/png</li>
281     * <li>image/svg+xml</li>
282     * <li>application/x-shockwave-flash</li>
283     * <li>application/font-woff</li>
284     * <li>application/x-font-ttf</li>
285     * <li>application/vnd.ms-fontobject</li>
286     * </ul>
287     */
288    @Contribute(CompressionAnalyzer.class)
289    public void disableCompressionForImageTypes(MappedConfiguration<String, Boolean> configuration)
290    {
291        configuration.add("image/*", false);
292        configuration.add("image/svg+xml", true);
293        configuration.add("application/x-shockwave-flash", false);
294        configuration.add("application/font-woff", false);
295        configuration.add("application/x-font-ttf", false);
296        configuration.add("application/vnd.ms-fontobject", false);
297    }
298
299    @Marker(ContextProvider.class)
300    public static AssetFactory buildContextAssetFactory(ApplicationGlobals globals,
301                                                        AssetPathConstructor assetPathConstructor,
302                                                        ResponseCompressionAnalyzer compressionAnalyzer,
303                                                        ResourceChangeTracker resourceChangeTracker,
304                                                        StreamableResourceSource streamableResourceSource)
305    {
306        return new ContextAssetFactory(compressionAnalyzer, resourceChangeTracker, streamableResourceSource, assetPathConstructor, globals.getContext());
307    }
308
309    @Contribute(ClasspathAssetAliasManager.class)
310    public static void addApplicationAndTapestryMappings(MappedConfiguration<String, String> configuration,
311
312                                                         @Symbol(TapestryHttpInternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
313                                                         String appPackage)
314    {
315        configuration.add("tapestry", "org/apache/tapestry5");
316
317        configuration.add("app", toPackagePath(appPackage));
318    }
319
320    /**
321     * Contributes an handler for each mapped classpath alias, as well handlers for context assets
322     * and stack assets (combined {@link org.apache.tapestry5.services.javascript.JavaScriptStack} files).
323     */
324    @Contribute(Dispatcher.class)
325    @AssetRequestDispatcher
326    public static void provideBuiltinAssetDispatchers(MappedConfiguration<String, AssetRequestHandler> configuration,
327
328                                                      @ContextProvider
329                                                      AssetFactory contextAssetFactory,
330
331                                                      @Autobuild
332                                                      StackAssetRequestHandler stackAssetRequestHandler,
333
334                                                      ClasspathAssetAliasManager classpathAssetAliasManager,
335                                                      ResourceStreamer streamer,
336                                                      AssetSource assetSource,
337                                                      ClasspathAssetProtectionRule classpathAssetProtectionRule,
338                                                      ContextAssetProtectionRule contextAssetProtectionRule)
339    {
340        Map<String, String> mappings = classpathAssetAliasManager.getMappings();
341
342        for (String folder : mappings.keySet())
343        {
344            String path = mappings.get(folder);
345
346            configuration.add(folder, new ClasspathAssetRequestHandler(streamer, assetSource, path, classpathAssetProtectionRule));
347        }
348
349        configuration.add(RequestConstants.CONTEXT_FOLDER,
350                new ContextAssetRequestHandler(streamer, contextAssetFactory.getRootResource(), contextAssetProtectionRule));
351
352        configuration.add(RequestConstants.STACK_FOLDER, stackAssetRequestHandler);
353
354    }
355
356    @Contribute(ClasspathAssetAliasManager.class)
357    public static void addMappingsForLibraryVirtualFolders(MappedConfiguration<String, String> configuration,
358                                                           ComponentClassResolver resolver)
359    {
360        // Each library gets a mapping or its folder automatically
361
362        Map<String, String> folderToPackageMapping = resolver.getFolderToPackageMapping();
363
364        for (String folder : folderToPackageMapping.keySet())
365        {
366            // This is the 5.3 version, which is still supported:
367            configuration.add(folder, toPackagePath(folderToPackageMapping.get(folder)));
368
369            // This is the 5.4 version; once 5.3 support is dropped, this can be simplified, and the
370            // "meta/" prefix stripped out.
371            String folderSuffix = folder.equals("") ? folder : "/" + folder;
372
373            configuration.add("meta" + folderSuffix, "META-INF/assets" + folderSuffix);
374        }
375    }
376
377    private static String toPackagePath(String packageName)
378    {
379        return packageName.replace('.', '/');
380    }
381
382    /**
383     * Contributes:
384     * <dl>
385     * <dt>ClientLocalization</dt>
386     * <dd>A virtual resource of formatting symbols for decimal numbers</dd>
387     * <dt>Core</dt>
388     * <dd>Built in messages used by Tapestry's default validators and components</dd>
389     * <dt>AppCatalog</dt>
390     * <dd>The Resource defined by {@link SymbolConstants#APPLICATION_CATALOG}</dd>
391     * <dt>
392     *     </dl>
393     *
394     * @since 5.2.0
395     */
396    @Contribute(ComponentMessagesSource.class)
397    public static void setupGlobalMessageCatalog(AssetSource assetSource,
398                                                 @Symbol(SymbolConstants.APPLICATION_CATALOG)
399                                                 Resource applicationCatalog, OrderedConfiguration<Resource> configuration)
400    {
401        configuration.add("ClientLocalization", new ClientLocalizationMessageResource());
402        configuration.add("Core", assetSource.resourceForPath("org/apache/tapestry5/core.properties"));
403        configuration.add("AppCatalog", applicationCatalog);
404    }
405
406    @Contribute(Dispatcher.class)
407    @Primary
408    public static void setupAssetDispatch(OrderedConfiguration<Dispatcher> configuration,
409                                          @AssetRequestDispatcher
410                                          Dispatcher assetDispatcher)
411    {
412
413        // This goes first because an asset to be streamed may have an file
414        // extension, such as
415        // ".html", that will confuse the later dispatchers.
416
417        configuration.add("Asset", assetDispatcher, "before:ComponentEvent");
418    }
419    
420    @Primary
421    public static ClasspathAssetProtectionRule buildClasspathAssetProtectionRule(
422            List<ClasspathAssetProtectionRule> rules, ChainBuilder chainBuilder)
423    {
424        return chainBuilder.build(ClasspathAssetProtectionRule.class, rules);
425    }
426
427    /**
428     * Sensitive resource types, blocked when the resource's file name ends with the (lower-case) suffix.
429     */
430    private static final Map<String, String> BLOCKED_FILE_SUFFIXES = blockedFileSuffixes();
431
432    private static Map<String, String> blockedFileSuffixes()
433    {
434        Map<String, String> map = new LinkedHashMap<>(22); // = 16 elements / 0.75 default load factor
435        map.put("ClassFile", ".class");
436        map.put("PropertiesFile", ".properties");
437        map.put("XMLFile", ".xml");
438        map.put("TemplateFile", ".tml");
439        map.put("MFFile", ".mf");
440        map.put("PEMFile", ".pem");
441        map.put("JKSFile", ".jks");
442        map.put("P12File", ".p12");
443        map.put("P8File", ".p8");
444        map.put("PKCS8File", ".pkcs8");
445        map.put("ASCFile", ".asc");
446        map.put("GPGFile", ".gpg");
447        map.put("PGPFile", ".pgp");
448        map.put("KeystoreFile", ".keystore");
449        map.put("KeyFile", ".key");
450        map.put("PFXFile", ".pfx");
451        return Collections.unmodifiableMap(map);
452    }
453
454     /**
455     * Sensitive resources, blocked by the complete (lower-case) resource's file name.
456     */
457    private static final Map<String, String> BLOCKED_FILE_NAMES = blockedFileNames();
458
459    private static Map<String, String> blockedFileNames()
460    {
461        Map<String, String> map = new LinkedHashMap<>();
462        map.put("DockerFile", "dockerfile");
463        map.put("IdRSAFile", "id_rsa");
464        map.put("IdEd25519File", "id_ed25519");
465        map.put("IdEcdsaFile", "id_ecdsa");
466        return Collections.unmodifiableMap(map);
467    }
468    public static void contributeClasspathAssetProtectionRule(
469            OrderedConfiguration<ClasspathAssetProtectionRule> configuration,
470            @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
471            boolean productionMode) 
472    {
473        BLOCKED_FILE_SUFFIXES.forEach((id, suffix) ->
474                configuration.add(id, (s) -> lowerCaseLastSegment(s).endsWith(suffix)));
475
476        // Block all filenames (and last-segment folders) starting with a dot.
477        configuration.add("Dotfile", (s) -> lowerCaseLastSegment(s).startsWith("."));
478
479        BLOCKED_FILE_NAMES.forEach((id, blocked) ->
480                configuration.add(id, (s) -> lowerCaseLastSegment(s).equals(blocked)));
481
482        // Requests that resolve to a directory (rather than a file) are refused directly by
483        // ClasspathAssetRequestHandler, which can inspect the actual resolved Resource.
484        // A path string alone cannot distinguish a folder from an extension-less file here.
485
486        // TAP5-2835: Don't serve source maps in production
487        if (productionMode)
488        {
489            ClasspathAssetProtectionRule mapFileRule = (s) -> lowerCaseLastSegment(s).endsWith(".map");
490            configuration.add("MapFile", mapFileRule);
491        }
492    }
493
494    /**
495     * Returns the last path segment (the file name), lower-cased, with any backslashes normalized
496     * to forward slashes first.
497     */
498    private static String lowerCaseLastSegment(String path)
499    {
500        path = path.replace('\\', '/');
501        final int lastIndex = path.lastIndexOf('/');
502        if (lastIndex >= 0)
503        {
504            path = path.substring(lastIndex + 1);
505        }
506        return path.toLowerCase();
507    }
508
509    @Primary
510    public static ContextAssetProtectionRule buildContextAssetProtectionRule(
511            List<ContextAssetProtectionRule> rules, ChainBuilder chainBuilder)
512    {
513        return chainBuilder.build(ContextAssetProtectionRule.class, rules);
514    }
515}