6 Creating custom mappers - Reference Documentation
Authors: Marc Palmer (marc@grailsrocks.com), Luke Daley (ld@ldaley.com), Peter N. Steinmetz (ndoc3@steinmetz.org)
Version: 1.2.14
Table of Contents
6 Creating custom mappers
The resource processing chain uses ResourceMapper artefacts to do the work.You can create your own ResourceMapper artefacts inside your application or plugins to perform tasks such as;- Moving and renaming resources
- Changing the contents of resource files (compress, minify, compile etc)
- Set HTTP response headers when requests are processed
6.1 Defining a mapper
Defining a resource mapper is easy. You create a file with ResourceMapper.groovy as the file suffix, in the grails-app/resourceMappers folder.The name of the mapper is extracted from the filename like any other Grails artefact, for example "TestResourceMapper" yields a mapper with name "test".Example grails-app/resourceMappers/TestResourceMapper.groovy:import org.grails.plugin.resource.mapper.MapperPhaseclass TestResourceMapper { def phase = MapperPhase.MUTATION def map(resource, config) { def file = new File(resource.processedFile.parentFile, "_${resource.processedFile.name}") assert resource.processedFile.renameTo(file) resource.processedFile = file resource.updateActualUrlFromProcessedFile() }}
grails.resources.mappers.<mappername>
.
NOTE: The name of the mapper is always in all lower case.This method can do whatever it needs to the resource's file, provided it calls the updateActualUrlFromProcessedFile() method if the resource moves, unless you patch ResourceMeta.actualUrl manually.You can change other properties of the resource, such as change the content type of the resource, add or modify tagAttributes (which are passed through when rendering the link for the resource).That's all you need to do to create a mapper. The best way to learn how they work is to study the source of Cached-Resources and Zipped-Resources plugins.
6.2 Mapper phases and priority
The example mapper shows the "phase" property being set to MapperPhase.MUTATION. The MapperPhase enumeration provides the possible mapping phases in the order in which they occur during processing of resources:enum MapperPhase { GENERATION, // create new assets = compile less files MUTATION, // alter/improve assets (may mean creating new/deleting aggregated resources) = spriting COMPRESSION, // reducing the file size but maintaining semantics = minify LINKNORMALISATION, // convert all inter asset references into a normal form = css links AGGREGATION, // combining mutiple assets into one = bundling RENAMING, // moving of physical assets = hashing LINKREALISATION, // convert normalised inter asset references into real form = css links ALTERNATEREPRESENTATION, // attach different representations of the asset = gzipping DISTRIBUTION, // moving assets to their hosting environment = s3, cdn ABSOLUTISATION, // update inter asset references to their distributed equivalent = css links NOTIFICATION // let the world know about the new resources = cache invalidation }
6.3 Operation
The optional "operation" property allows you to specify the name of the kind of work performed by the mapper. Users can then prevent any mappers of this operation from executing on their resources.The common example is to specify exclude:"minify" in a resource declaration to prevent any kind of minifying mapper from being applied to a resource that is already minified.A similar operation called e.g. "compress" could be used to prevent duplicate zipping of resources that may have been pre-compressed (such as images).import org.grails.plugin.resource.mapper.MapperPhaseclass TestResourceMapper { def phase = MapperPhase.COMPRESS def operation = "compression" def map(resource, config) { // Zip the file here }}
6.4 Processing only the right types of files
Often a mapper is only meant to target certain file types or file patterns. Make it easier for your users by operating correctly out of the box by specifying defaultExcludes and/or defaultIncludes for your mapper, which will filter the resources passed to your mapper:import org.grails.plugin.resource.mapper.MapperPhaseclass TestResourceMapper { def phase = MapperPhase.MUTATION static defaultExcludes = [ '**/*.png', '**/*.gif', '**/*.jpg', '**/*.jpeg', '**/*.gz', '**/*.zip' ] static defaultIncludes = [ '/images/**' ] def map(resource, config) { … }
6.5 Adding response headers and intercepting requests
Resource mappers also have the opportunity to take part in response handling so that they can adjust the response headers if necessary. You may need to adapt the handling of the current request to the request headers supplied.Note that this cannot be used to change how the resource is mapped - the mapping is performed once only, but the response headers can be customized every time the file is requested.As an example, the Zipped-Resources plugin uses this mechanism to set the Content-Encoding header:import org.grails.plugin.resource.mapper.MapperPhaseclass ZipResourceMapper { static phase = MapperPhase.ALTERNATEREPRESENTATION /** * Rename the file to a hash of it's contents, and set caching headers */ def map(resource, config) { // Do the zipping ... // Set up response headers resource.requestProcessors << { req, resp -> resp.setHeader('Content-Encoding', 'gzip') } } }