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}