├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── ResourcesGrailsPlugin.groovy ├── application.properties ├── docs └── Core Concepts.gdoc ├── grails-app ├── conf │ ├── BlueprintResources.groovy │ ├── BuildConfig.groovy │ ├── Config.groovy │ ├── DataSource.groovy │ ├── JQueryResources.groovy │ ├── META-INF │ │ └── mime.types │ ├── OverrideResources.groovy │ ├── ResourcesBootStrap.groovy │ ├── TestOnlyResources.groovy │ └── UrlMappings.groovy ├── i18n │ └── messages.properties ├── resourceMappers │ └── org │ │ └── grails │ │ └── plugin │ │ └── resource │ │ ├── BaseUrlResourceMapper.groovy │ │ ├── BundleResourceMapper.groovy │ │ ├── CSSPreprocessorResourceMapper.groovy │ │ ├── CSSRewriterResourceMapper.groovy │ │ └── test │ │ └── TestResourceMapper.groovy ├── taglib │ └── org │ │ └── grails │ │ └── plugin │ │ └── resource │ │ └── ResourceTagLib.groovy └── views │ └── index.gsp ├── grailsw ├── grailsw.bat ├── src ├── docs │ ├── guide │ │ ├── 1. Overview.gdoc │ │ ├── 1.1 Quick Start.gdoc │ │ ├── 1.1.1 Make sure jQuery plugin is installed.gdoc │ │ ├── 1.1.2 Install jQuery UI and Blueprint plugins.gdoc │ │ ├── 1.1.3 Edit your Sitemesh layout.gdoc │ │ ├── 1.1.4 Edit your GSP page to include jQuery.gdoc │ │ ├── 1.1.5 View the page source.gdoc │ │ ├── 1.1.6 Now optimize your application.gdoc │ │ ├── 10. Security.gdoc │ │ ├── 2. Concepts.gdoc │ │ ├── 3. Declaring resources.gdoc │ │ ├── 3.1 The resource DSL.gdoc │ │ ├── 3.1.1 The dependsOn method.gdoc │ │ ├── 3.1.2 The resource method.gdoc │ │ ├── 3.1.3 The defaultBundle method.gdoc │ │ ├── 3.2 Resource artefacts.gdoc │ │ ├── 3.3 Config.groovy.gdoc │ │ ├── 3.4 Bundling.gdoc │ │ ├── 4. Using resources.gdoc │ │ ├── 4.1 Linking to CSS, JavaScript etc..gdoc │ │ ├── 4.2 Linking to images.gdoc │ │ ├── 4.3 Linking to resources explicitly, bypassing modules.gdoc │ │ ├── 4.4 Including pieces of JavaScript code generated at runtime.gdoc │ │ ├── 5. Overriding resources.gdoc │ │ ├── 6. Creating custom mappers.gdoc │ │ ├── 6.1 Defining a mapper.gdoc │ │ ├── 6.2 Mapper phases and priority.gdoc │ │ ├── 6.3 Operation.gdoc │ │ ├── 6.4 Processing only the right types of files.gdoc │ │ ├── 6.5 Adding response headers and intercepting requests.gdoc │ │ ├── 7. Writing plugins that use Resources.gdoc │ │ ├── 8. Debugging.gdoc │ │ └── 9. Configuration.gdoc │ └── ref │ │ ├── Mappers │ │ ├── baseurl.gdoc │ │ ├── bundle.gdoc │ │ ├── csspreprocessor.gdoc │ │ └── cssrewriter.gdoc │ │ └── Tags │ │ ├── external.gdoc │ │ ├── img.gdoc │ │ ├── layoutResources.gdoc │ │ ├── require.gdoc │ │ ├── resource.gdoc │ │ ├── resourceLink.gdoc │ │ ├── script.gdoc │ │ ├── stash.gdoc │ │ ├── style.gdoc │ │ └── use.gdoc ├── groovy │ └── org │ │ └── grails │ │ └── plugin │ │ └── resource │ │ ├── AggregatedResourceMeta.groovy │ │ ├── CSSBundleResourceMeta.groovy │ │ ├── CSSLinkProcessor.groovy │ │ ├── DevModeSanityFilter.groovy │ │ ├── JavaScriptBundleResourceMeta.groovy │ │ ├── ProcessingFilter.groovy │ │ ├── RequestUtil.java │ │ ├── ResourceMeta.groovy │ │ ├── ResourceModule.groovy │ │ ├── ResourceProcessor.groovy │ │ ├── ResourceProcessorBatch.groovy │ │ ├── URLUtils.java │ │ ├── mapper │ │ ├── MapperPhase.groovy │ │ ├── ResourceMapper.groovy │ │ └── ResourceMappersFactory.groovy │ │ ├── module │ │ ├── ModuleBuilder.groovy │ │ ├── ModuleDeclarationsFactory.groovy │ │ └── ModulesBuilder.groovy │ │ └── util │ │ ├── DispositionsUtils.groovy │ │ ├── HalfBakedLegacyLinkGenerator.groovy │ │ ├── ResourceMetaStore.groovy │ │ └── StatsManager.groovy └── java │ └── org │ └── grails │ └── plugin │ └── resources │ ├── artefacts │ ├── AbstractResourcesArtefactHandler.java │ ├── DefaultResourceMapperClass.java │ ├── DefaultResourcesClass.java │ ├── ResourceMapperArtefactHandler.java │ ├── ResourceMapperClass.java │ ├── ResourcesArtefactHandler.java │ └── ResourcesClass.java │ └── stash │ ├── ScriptStashWriter.java │ ├── StashManager.java │ ├── StashWriter.java │ └── StyleStashWriter.java ├── test ├── integration │ ├── InitialisationSmokeTests.groovy │ └── org │ │ └── grails │ │ └── plugin │ │ ├── resource │ │ ├── LegacyResourceIntegrationSpec.groovy │ │ ├── ResourceProcessorIntegTests.groovy │ │ ├── ResourceTagLibIntegTests.groovy │ │ └── module │ │ │ └── ModulesIntegTests.groovy │ │ └── resources │ │ └── stash │ │ └── StashManagerIntegrationTests.groovy ├── test-files │ ├── image.png │ └── somehack.xml └── unit │ └── org │ └── grails │ └── plugin │ ├── resource │ ├── AggregatedResourceMetaTests.groovy │ ├── BaseUrlResourceMapperSpec.groovy │ ├── BundleResourceMapperTests.groovy │ ├── CSSLinkProcessorTests.groovy │ ├── CSSPreprocessorResourceMapperTests.groovy │ ├── CSSRewriterResourceMapperTests.groovy │ ├── PathMatcherTests.groovy │ ├── ProcessingFilterTests.groovy │ ├── ResourceMapperTests.groovy │ ├── ResourceMetaStoreTests.groovy │ ├── ResourceMetaTests.groovy │ ├── ResourceModuleTests.groovy │ ├── ResourceModulesBuilderTests.groovy │ ├── ResourceProcessorSpec.groovy │ ├── ResourceProcessorTests.groovy │ ├── ResourceTagLibTests.groovy │ ├── URLUtilsSpec.groovy │ ├── URLUtilsTests.groovy │ └── util │ │ └── DispositionsUtilsTests.groovy │ └── resources │ └── stash │ ├── ScriptStashWriterUnitTests.groovy │ └── StyleStashWriterUnitTests.groovy ├── travis-build.sh ├── web-app ├── GPRESOURCES-207 │ ├── file1.js │ ├── file2.js │ ├── file3.js │ ├── file4.js │ └── file5.js ├── GPRESOURCES-210 │ ├── file1.js │ └── file2.js ├── WEB-INF │ ├── applicationContext.xml │ ├── sitemesh.xml │ └── tld │ │ ├── c.tld │ │ ├── fmt.tld │ │ ├── grails.tld │ │ └── spring.tld ├── css │ ├── blueprint │ │ ├── ie.css │ │ ├── screen.css │ │ └── src │ │ │ └── grid.png │ ├── errors.css │ ├── legacy.css │ ├── main.css │ └── mobile.css ├── images │ ├── apple-touch-icon-retina.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── grails_logo.jpg │ ├── grails_logo.png │ ├── leftnav_btm.png │ ├── leftnav_midstretch.png │ ├── leftnav_top.png │ ├── skin │ │ ├── database_add.png │ │ ├── database_delete.png │ │ ├── database_edit.png │ │ ├── database_save.png │ │ ├── database_table.png │ │ ├── exclamation.png │ │ ├── house.png │ │ ├── information.png │ │ ├── shadow.jpg │ │ ├── sorted_asc.gif │ │ └── sorted_desc.gif │ ├── spinner.gif │ └── springsource.png └── js │ ├── adhoc.js │ ├── application.js │ ├── core.js │ ├── jquery-ui │ ├── jquery-ui-1.8.5.custom.min.js │ └── themes │ │ └── custom-theme │ │ ├── images │ │ ├── ui-bg_diagonals-thick_90_eeeeee_40x40.png │ │ ├── ui-bg_flat_15_cd0a0a_40x100.png │ │ ├── ui-bg_glass_100_e4f1fb_1x400.png │ │ ├── ui-bg_glass_50_3baae3_1x400.png │ │ ├── ui-bg_glass_80_d7ebf9_1x400.png │ │ ├── ui-bg_highlight-hard_100_f2f5f7_1x100.png │ │ ├── ui-bg_highlight-hard_70_000000_1x100.png │ │ ├── ui-bg_highlight-soft_100_deedf7_1x100.png │ │ ├── ui-bg_highlight-soft_25_ffef8f_1x100.png │ │ ├── ui-icons_2694e8_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_3d80b3_256x240.png │ │ ├── ui-icons_72a7cf_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ │ └── jquery-ui-1.8.5.custom.css │ └── jquery │ ├── jquery-1.4.2-b.min.js │ └── jquery-1.4.2.min.js └── wrapper ├── grails-wrapper-runtime-2.3.11.jar ├── grails-wrapper.properties └── springloaded-1.2.0.RELEASE.jar /.gitignore: -------------------------------------------------------------------------------- 1 | #IDEA 2 | .idea/ 3 | *.iml 4 | *.ipr 5 | *.iws 6 | 7 | #Eclipse 8 | .settings/ 9 | .classpath 10 | .project 11 | 12 | #compiled sources 13 | target/* 14 | target-eclipse/* 15 | out/* 16 | *.zip 17 | 18 | #logs 19 | *.log 20 | 21 | #other 22 | plugin.xml 23 | dependencies.groovy 24 | /test-tmp/ 25 | /src/docs/ref/Resources/ 26 | *.swp 27 | 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: groovy 2 | jdk: 3 | - openjdk7 4 | - openjdk6 5 | - oraclejdk7 6 | before_script: 7 | - rm -rf target 8 | script: ./travis-build.sh 9 | env: 10 | global: 11 | - GIT_NAME="Graeme Rocher" 12 | - GIT_EMAIL="graeme.rocher@gmail.com" 13 | - secure: LrVonKDVyNsS3GrErpKeqicnRlwiXQi/LylPL0now266SM1E3clBGyZxA89g7ItqFpqQivwANysTpRL0nO8gd+EkyVG5B19P9Fq0eELLglhinU4T/T5MmWl0a2GFQSyewZDjJDrQQl1zMG3ejbl9QrFauwM4NfPcVMpyON3wjOY= 14 | - secure: eqMOkrSZOS44IZj6cK5i8/JCneWyMRCuhq7Gq+AMSIJFiw6c9ZMkooco/LLL2OV7D9f6yYmZXSDXWLv1scy9/YqrQftYrI7VyVA4Zwb4+MGag2kJ/T7jRtQ/FkYmSBex6VRuzGPN+ZyTdjuaJm3TJoCZwWfGE3Sw9OPYRRLEZAA= 15 | - secure: Ieprqq+UrMPivvu4cZXK4h+Vwl6QTTv/RYKFJddoN7FxHYoSqxXNNz3MBB0OS3DDM3jyHPLapvolWh8/4zis9hKl8R3C2Al00hiNBRtH0urx+IamvUcki6VEl/NSd99QQGCxqZn+zCH//N4nO5bS/u4fVnP5LfC+KBshdbPFrkU= 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | (c) 2009 Marc Palmer / AnyWare Ltd. www.grailsrocks.com 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Build Status](https://api.travis-ci.org/gpc/grails-mail.png)](http://travis-ci.org/grails-plugins/grails-resources) 4 | 5 | Grails Resources framework. 6 | =========================== 7 | 8 | Grails resources provides a DSL for declaring static resources in plugins and in apps, and a mapper artefact that allows other plugins to provide processing of static resources to e.g. minify / zip them. 9 | 10 | All processing is performed at runtime once, against files in the server filesystem. 11 | 12 | Built-in mappers included in the plugin: 13 | 14 | * CSS rewriter (two mappers required, happens automatically) 15 | * Bundler (combines multiple css or js files into one) 16 | 17 | See docs at http://grails-plugins.github.io/grails-resources/ 18 | -------------------------------------------------------------------------------- /application.properties: -------------------------------------------------------------------------------- 1 | #Grails Metadata file 2 | #Sat Sep 14 14:20:38 MST 2013 3 | app.grails.version=2.3.11 4 | app.name=resources 5 | -------------------------------------------------------------------------------- /docs/Core Concepts.gdoc: -------------------------------------------------------------------------------- 1 | h1. Core Concepts 2 | 3 | h2. Declared resources 4 | 5 | Normally you will declare most of your resources (or plugins that provide 6 | libraries will do this for you), and the Resources framework will know about 7 | your application's core resources in advance. 8 | 9 | These resources will be processed at startup up and be ready to go from the 10 | moment your application is able to receive requests. 11 | 12 | You reference these resources in your views using the resource tags e.g. 13 | r:require, r:img, r:resource, r:resourceLink. 14 | 15 | h2. Ad-hoc resources 16 | 17 | Some resources are not declared in advance. These are typically images that 18 | you use throughout your HTML but do not wish to declare in advance, as well as 19 | those referenced by CSS files you have declared. 20 | 21 | These are called "ad-hoc" resources because you have provided no formal 22 | resource definition for them. As such they assume default behaviour for their 23 | resource type, and if you need to alter this behaviour (for example provide 24 | extra HTML attributes for them), you should declare them explicitly. 25 | 26 | You link to ad-hoc resources either implicitly by including, for example, a 27 | CSS file that references other image resources, or by using the r:img, 28 | r:resource or r:resourceLink tags. 29 | 30 | h2. Legacy resources 31 | 32 | With existing applications and plugins that do not use the Resources 33 | framework, the r:xxx tags are not used and few if any resources are declared. 34 | 35 | In this eventuality, Resources framework intercepts these requests and issues 36 | redirects to the processed versions of these resources. This means that 37 | regular tags, CSS, 10 | 11 | {code} 12 | 13 | This would output the "); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/java/org/grails/plugin/resources/stash/StashManager.java: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resources.stash; 2 | 3 | import org.grails.plugin.resource.util.DispositionsUtils; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.io.IOException; 7 | import java.io.Writer; 8 | import java.util.*; 9 | 10 | /** 11 | * Manages the stashing and unstashing of page fragments. 12 | * 13 | * @author Patrick Jungermann 14 | */ 15 | public class StashManager { 16 | 17 | /** 18 | * Prefix used for storing page fragment stashes. 19 | */ 20 | public static final String REQ_ATTR_PREFIX_PAGE_FRAGMENTS = "resources.plugin.page.fragments"; 21 | 22 | /** 23 | * Registered, usable stash writers. 24 | */ 25 | public static final Map STASH_WRITERS = new HashMap(); 26 | static { 27 | // register the basic stash writers 28 | STASH_WRITERS.put("script", new ScriptStashWriter()); 29 | STASH_WRITERS.put("style", new StyleStashWriter()); 30 | } 31 | 32 | /** 33 | * Stashes a page fragment. 34 | * 35 | * @param request The current request, at which the page fragment has to be stashed. 36 | * @param type The stash's (writer) type. 37 | * @param disposition The disposition, at which the page fragment has to be unstashed. 38 | * @param fragment The fragment, which has to be stashed. 39 | */ 40 | @SuppressWarnings("unchecked") 41 | public static void stashPageFragment(final HttpServletRequest request, final String type, final String disposition, final String fragment) { 42 | final String resourceTrackerName = makePageFragmentKey(type, disposition); 43 | DispositionsUtils.addDispositionToRequest(request, disposition, "-page-fragments-"); 44 | 45 | List resourceTracker = (List) request.getAttribute(resourceTrackerName); 46 | if (resourceTracker == null) { 47 | resourceTracker = new ArrayList(); 48 | request.setAttribute(resourceTrackerName, resourceTracker); 49 | } 50 | resourceTracker.add(fragment); 51 | } 52 | 53 | /** 54 | * Unstashes the disposition's page fragments. 55 | * 56 | * @param out The target, to which all fragments have to be written to. 57 | * @param request The request, at which the fragments have been stashed. 58 | * @param disposition The disposition, for which all fragments have to be rendered. 59 | * @throws IOException if there was any problem with writing the fragments. 60 | */ 61 | public static void unstashPageFragments(final Writer out, final HttpServletRequest request, final String disposition) throws IOException { 62 | for (final String type : STASH_WRITERS.keySet()) { 63 | List stash = consumePageFragments(request, type, disposition); 64 | if (!stash.isEmpty()) { 65 | STASH_WRITERS.get(type).write(out, stash); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Returns the stash (all page fragments) of the requested type and disposition. 72 | * 73 | * @param request The request, at which the fragments have been stashed. 74 | * @param type The fragments' type. 75 | * @param disposition The disposition, for which the fragments have to be returned. 76 | * @return All fragments of the requested type and disposition. 77 | */ 78 | @SuppressWarnings("unchecked") 79 | private static List consumePageFragments(final HttpServletRequest request, final String type, final String disposition) { 80 | List stash = (List) request.getAttribute(makePageFragmentKey(type, disposition)); 81 | return stash != null ? stash : Collections.EMPTY_LIST; 82 | } 83 | 84 | /** 85 | * Returns the page fragment key, used to store the page fragment stashes. 86 | * 87 | * @param type The fragments' type. 88 | * @param disposition The fragments' disposition. 89 | * @return The page fragment key. 90 | */ 91 | private static String makePageFragmentKey(final String type, final String disposition) { 92 | return REQ_ATTR_PREFIX_PAGE_FRAGMENTS + ":" + type + ":" + disposition; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/java/org/grails/plugin/resources/stash/StashWriter.java: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resources.stash; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | import java.util.List; 6 | 7 | /** 8 | * Writes a stash to the output. 9 | * 10 | * @author Patrick Jungermann 11 | */ 12 | public interface StashWriter { 13 | 14 | /** 15 | * Writes the stash's content to the writer. 16 | * 17 | * @param out The output writer. 18 | * @param stash The stash. 19 | * @throws IOException if there was a problem with writing the content to the writer. 20 | */ 21 | void write(Writer out, List stash) throws IOException; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/java/org/grails/plugin/resources/stash/StyleStashWriter.java: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resources.stash; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | import java.util.List; 6 | 7 | /** 8 | * Writes stashed styles to the output. All stashed fragments will be written into one style tag, in order. 9 | * 10 | * @author Patrick Jungermann 11 | */ 12 | public class StyleStashWriter implements StashWriter { 13 | 14 | @Override 15 | public void write(final Writer out, final List stash) throws IOException { 16 | if (stash.isEmpty()) { 17 | return; 18 | } 19 | 20 | out.write(""); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /test/integration/InitialisationSmokeTests.groovy: -------------------------------------------------------------------------------- 1 | import grails.test.mixin.integration.IntegrationTestMixin 2 | import grails.test.mixin.TestMixin 3 | 4 | @TestMixin(IntegrationTestMixin) 5 | class InitialisationSmokeTests { 6 | 7 | def grailsResourceProcessor 8 | 9 | /** 10 | * We are testing that the resources plugin operates correctly in an integration testing environment. 11 | * That is, it does not cause issues for users when installed and they are running integration tests. 12 | * 13 | * @see TestOnlyResources 14 | */ 15 | void testInitialisedOk() { 16 | //@todo this temporarily removed until Grails fixes the problems with servletContext resource loading/another workaround found 17 | //assert grailsResourceProcessor.getModule("jquery") != null 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /test/integration/org/grails/plugin/resource/LegacyResourceIntegrationSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.spock.IntegrationSpec 4 | import org.codehaus.groovy.grails.commons.GrailsApplication 5 | import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletRequest 6 | import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse 7 | 8 | class LegacyResourceIntegrationSpec extends IntegrationSpec { 9 | 10 | ResourceProcessor grailsResourceProcessor 11 | GrailsApplication grailsApplication 12 | 13 | // GPRESOURCES-214 14 | def 'legacy resource with baseurl'() { 15 | grailsApplication.config.grails.resources.mappers.baseurl.enabled = true 16 | grailsApplication.config.grails.resources.mappers.baseurl.default = "http://cdn.domain.com/static" 17 | 18 | GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest() 19 | GrailsMockHttpServletResponse response = new GrailsMockHttpServletResponse() 20 | 21 | request.requestURI = "/images/springsource.png" 22 | 23 | when: 24 | grailsResourceProcessor.processLegacyResource(request, response) 25 | 26 | then: 27 | response.redirectedUrl == "http://cdn.domain.com/static/images/_springsource.png" 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /test/integration/org/grails/plugin/resource/ResourceProcessorIntegTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.integration.IntegrationTestMixin 4 | import grails.test.mixin.TestMixin 5 | 6 | @TestMixin(IntegrationTestMixin) 7 | class ResourceProcessorIntegTests { 8 | 9 | def grailsResourceProcessor 10 | 11 | protected makeMockResource(uri) { 12 | [ 13 | uri:uri, 14 | disposition:'head', 15 | exists: { -> true } 16 | ] 17 | } 18 | 19 | void testGettingModulesInDependencyOrder() { 20 | def testModules = [ 21 | a: [name:'a', resources: [ makeMockResource('a.css') ] ], 22 | b: [name:'b', dependsOn:['a'], resources: [ makeMockResource('b.css') ] ], 23 | c: [name:'c', dependsOn:['a', 'b'], resources: [ makeMockResource('a.css') ] ], 24 | d: [name:'d', dependsOn:['b'], resources: [ makeMockResource('a.css') ] ], 25 | e: [name:'e', dependsOn:['d'], resources: [ makeMockResource('a.css') ] ] 26 | ] 27 | 28 | def modsNeeded = [ 29 | e: true, 30 | c: true 31 | ] 32 | 33 | grailsResourceProcessor.modulesByName.putAll(testModules) 34 | grailsResourceProcessor.updateDependencyOrder() 35 | 36 | def moduleNames = grailsResourceProcessor.getAllModuleNamesRequired(modsNeeded) 37 | println "Module names: ${moduleNames}" 38 | def moduleNameResults = grailsResourceProcessor.getModulesInDependencyOrder(moduleNames) 39 | println "Modules: ${moduleNameResults}" 40 | 41 | assert moduleNameResults.indexOf('a') < moduleNameResults.indexOf('b') 42 | assert moduleNameResults.indexOf('b') < moduleNameResults.indexOf('c') 43 | assert moduleNameResults.indexOf('b') < moduleNameResults.indexOf('d') 44 | assert moduleNameResults.indexOf('b') < moduleNameResults.indexOf('e') 45 | assert moduleNameResults.indexOf('d') < moduleNameResults.indexOf('e') 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/integration/org/grails/plugin/resource/module/ModulesIntegTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource.module 2 | 3 | import grails.test.GroovyPagesTestCase 4 | import grails.test.mixin.integration.IntegrationTestMixin 5 | import grails.test.mixin.TestMixin 6 | 7 | /** 8 | * Integration tests of constructing modules. 9 | * 10 | * @author peter 11 | */ 12 | @TestMixin(IntegrationTestMixin) 13 | class ModulesIntegTests extends GroovyPagesTestCase { 14 | 15 | def grailsResourceProcessor 16 | def grailsApplication 17 | 18 | protected makeMockResource(uri) { 19 | [ 20 | uri:uri, 21 | disposition:'head', 22 | exists: { -> true } 23 | ] 24 | } 25 | 26 | void testGrailsApplicationAccessInClosure() { 27 | 28 | def template = ''' 29 | 30 | 31 | 32 | 33 | 34 |

Hi

35 | 36 | ''' 37 | def result = applyTemplate(template, [:]) 38 | 39 | assertTrue result.contains("") 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /test/test-files/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/test/test-files/image.png -------------------------------------------------------------------------------- /test/test-files/somehack.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/AggregatedResourceMetaTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | import grails.test.mixin.TestMixin 3 | import grails.test.mixin.support.GrailsUnitTestMixin 4 | import org.junit.Rule 5 | import org.junit.rules.TemporaryFolder 6 | 7 | @TestMixin(GrailsUnitTestMixin) 8 | class AggregatedResourceMetaTests { 9 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder() 10 | File temporarySubfolder 11 | 12 | def mockResSvc 13 | def module 14 | 15 | @org.junit.Before 16 | void setupTest() { 17 | temporarySubfolder = temporaryFolder.newFolder('test-tmp') 18 | 19 | module = new ResourceModule() 20 | module.name = 'aggmodule' 21 | 22 | mockResSvc = [ 23 | config : [ ], 24 | updateDependencyOrder: { -> }, 25 | modulesInDependencyOrder: [module.name], 26 | getMimeType: { String str -> 'text/plain' }, 27 | makeFileForURI: { uri -> new File(temporarySubfolder, uri)} 28 | ] 29 | 30 | } 31 | 32 | protected ResourceMeta makeRes(String reluri, String contents) { 33 | def base = new File('./test-tmp/') 34 | base.mkdirs() 35 | 36 | def r = new ResourceMeta(sourceUrl:'/'+reluri) 37 | r.workDir = base 38 | r.actualUrl = r.sourceUrl 39 | r.disposition = 'head' 40 | r.contentType = "text/css" 41 | r.processedFile = new File(base, reluri) 42 | r.processedFile.parentFile.mkdirs() 43 | r.processedFile.delete() 44 | r.module = module 45 | 46 | r.processedFile << new ByteArrayInputStream(contents.bytes) 47 | return r 48 | } 49 | 50 | /** 51 | * Ensure that bundle mapper updates content length and exists() 52 | */ 53 | void testUpdatesMetadata() { 54 | def r = new AggregatedResourceMeta() 55 | 56 | def r1 = makeRes('/aggtest/file1.css', "/* file 1 */") 57 | def r2 = makeRes('/aggtest/file2.css', "/* file 2 */") 58 | 59 | r.add(r1) 60 | r.add(r2) 61 | 62 | r.sourceUrl = '/aggtest1.css' 63 | assertFalse r.exists() 64 | 65 | r.beginPrepare(mockResSvc) 66 | 67 | r.endPrepare(mockResSvc) 68 | 69 | assertTrue r.exists() 70 | assertTrue r.contentLength >= r1.contentLength + r2.contentLength 71 | } 72 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/BaseUrlResourceMapperSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import spock.lang.Specification 4 | 5 | class BaseUrlResourceMapperSpec extends Specification { 6 | 7 | def mapper 8 | 9 | def setup(){ 10 | mapper = new BaseUrlResourceMapper() 11 | } 12 | 13 | def "test that mappers are configured correctly"(){ 14 | setup: 15 | def resource = [ linkUrl : '/images.jpg' ] 16 | def config = [ enabled: true, default: 'http://www.google.com/' ] 17 | when: 18 | mapper.map( resource, config ) 19 | then: 20 | resource.linkOverride == 'http://www.google.com/images.jpg' 21 | } 22 | 23 | def "when mappers are disabled, links are not processed"(){ 24 | setup: 25 | def resource = [ linkUrl : '/images.jpg' ] 26 | def config = [ enabled: false, default: 'http://www.google.com/' ] 27 | when: 28 | mapper.map( resource, config ) 29 | then: 30 | resource.linkOverride == null 31 | } 32 | 33 | def "a resource can set a unique url based on module name"(){ 34 | setup: 35 | def resource = [ linkUrl : '/images.jpg', module: [ name: 'uno'] ] 36 | def config = [ enabled: true, default:'http://www.google.com/', modules : [ uno: 'http://uno.com/' ] ] 37 | when: 38 | mapper.map( resource, config ) 39 | then: 40 | resource.linkOverride == 'http://uno.com/images.jpg' 41 | } 42 | 43 | def "a resource with no modules default to base url"(){ 44 | setup: 45 | def resource = [ linkUrl : '/images.jpg', module: [ name: 'uno'] ] 46 | def config = [ enabled: true, default:'http://www.google.com/', modules : [ dos: 'http://dos.com/' ] ] 47 | when: 48 | mapper.map( resource, config ) 49 | then: 50 | resource.linkOverride == 'http://www.google.com/images.jpg' 51 | } 52 | 53 | //GPRESOURCES-184 54 | def "mapper uses delegate-resource's name for aggreagated resources"() { 55 | setup: 56 | def resourceBundle = [getLinkUrl: { '/bundle.js' }] as AggregatedResourceMeta 57 | resourceBundle.resources = [bundledResource('uno')] 58 | def config = [ enabled: true, default:'http://www.google.com/', modules : [ uno: 'http://uno.com/' ] ] 59 | 60 | when: 61 | mapper.map(resourceBundle, config) 62 | 63 | then: 64 | resourceBundle.linkOverride == 'http://uno.com/bundle.js' 65 | } 66 | 67 | //GPRESOURCES-184 68 | def "mapper throws an exception when configured to map modules bundled together to different urls"() { 69 | setup: 70 | def resourceBundle = [getLinkUrl: { '/bundle.js' }] as AggregatedResourceMeta 71 | resourceBundle.resources = [bundledResource('uno'), bundledResource('dos')] 72 | def config = [ enabled: true, default:'http://www.google.com/', modules : [ uno: 'http://uno.com/' ] ] 73 | 74 | when: 75 | mapper.map(resourceBundle, config) 76 | 77 | then: 78 | def exception = thrown(IllegalArgumentException) 79 | exception.message.contains('All modules bundled together must have the same baseUrl override') 80 | exception.message.contains(resourceBundle.resources.first().bundle) 81 | } 82 | 83 | //GPRESOURCES-16 84 | def "mapper uses rotating baseUrl but only when config.default configured as a list"() { 85 | setup: 86 | def resource = [ linkUrl : '/images.jpg' ] 87 | def config = [ enabled: true, default: 'http://www.google.com/' ] 88 | 89 | when: 90 | mapper.map( resource, config ) 91 | 92 | then: 93 | resource.linkOverride == 'http://www.google.com/images.jpg' 94 | } 95 | 96 | def "mapper uses rotating baseUrl but only when config.default configured as a non-empty list"() { 97 | setup: 98 | def resource = [ linkUrl : '/images.jpg' ] 99 | def config = [ enabled: true, default: [] ] 100 | 101 | when: 102 | mapper.map( resource, config ) 103 | 104 | then: 105 | resource.linkOverride == null 106 | } 107 | 108 | def "mapper uses rotating baseUrl when baseUrl configured as a list"() { 109 | setup: 110 | def resourceOne = [ linkUrl : '/images.jpg' ] 111 | def resourceTwo = [ linkUrl : '/images.png' ] 112 | def resourceThree = [ linkUrl : '/images.gif' ] 113 | def config = [ enabled: true, default: ['http://cdn1.google.com/', 'http://cdn2.google.com/', 'http://cdn3.google.com/']] 114 | 115 | when: 116 | mapper.map( resourceOne, config ) 117 | mapper.map( resourceTwo, config ) 118 | mapper.map( resourceThree, config ) 119 | 120 | then: 121 | resourceOne.linkOverride == 'http://cdn3.google.com/images.jpg' 122 | resourceTwo.linkOverride == 'http://cdn3.google.com/images.png' 123 | resourceThree.linkOverride == 'http://cdn2.google.com/images.gif' 124 | } 125 | //GPRESOURCES-16 126 | 127 | private ResourceMeta bundledResource(String moduleName) { 128 | def module = [name: moduleName] as ResourceModule 129 | [module: module, bundle: 'bundle_head'] as ResourceMeta 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/BundleResourceMapperTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin; 5 | 6 | public @TestMixin(GrailsUnitTestMixin) 7 | class BundleResourceMapperTests { 8 | 9 | BundleResourceMapper mapper = new BundleResourceMapper() 10 | 11 | void testRecognisedArtifactsAreBundledIfRequested() { 12 | 13 | Map expectedBundling = [ 14 | 'text/css': true, 15 | 'text/javascript': true, 16 | 'application/javascript': true, 17 | 'application/x-javascript': true, 18 | 'everything/nothing': false 19 | ] 20 | 21 | List resultingBundle 22 | Map grailsResourceProcessor = [ 23 | findSyntheticResourceById: { String bundleId -> return resultingBundle }, 24 | ] 25 | 26 | mapper.grailsResourceProcessor = grailsResourceProcessor 27 | 28 | expectedBundling.each { String resourceType, Boolean shouldBundle -> 29 | resultingBundle = [[existing:'bundle']] 30 | Map resource = [identifier:UUID.randomUUID(), contentType: resourceType, bundle:'myBundle', sourceUrlExtension:'js'] 31 | 32 | mapper.map(resource, null) 33 | 34 | assert (resultingBundle[1]?.identifier == resource.identifier) == shouldBundle 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/CSSLinkProcessorTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | import grails.test.mixin.TestMixin 3 | import grails.test.mixin.support.GrailsUnitTestMixin 4 | import org.junit.Rule 5 | import org.junit.rules.TemporaryFolder 6 | 7 | @TestMixin(GrailsUnitTestMixin) 8 | class CSSLinkProcessorTests { 9 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder() 10 | File temporarySubfolder 11 | def mockResSvc 12 | 13 | @org.junit.Before 14 | void setupTest() { 15 | temporarySubfolder = temporaryFolder.newFolder('test-tmp') 16 | mockResSvc = [ 17 | config : [ rewrite: [css: true] ] 18 | ] 19 | } 20 | 21 | protected ResourceMeta makeRes(String reluri, String contents) { 22 | def r = new ResourceMeta(sourceUrl:'/'+reluri) 23 | r.workDir = temporarySubfolder 24 | r.actualUrl = r.sourceUrl 25 | r.contentType = "text/css" 26 | r.processedFile = new File(temporarySubfolder, reluri) 27 | r.processedFile.parentFile.mkdirs() 28 | r.processedFile.delete() 29 | 30 | r.processedFile << new ByteArrayInputStream(contents.bytes) 31 | return r 32 | } 33 | 34 | /** 35 | * This simulates a test where the image resources are moved to a new flat dir 36 | * but the CSS is *not* moved, to force recalculation of paths 37 | */ 38 | void testCSSPreprocessing() { 39 | 40 | def res = makeRes('css/urltests1.css', """ 41 | @import '/css/style1.css'; 42 | @import "/css/style2.css"; 43 | @import '/css/style3.css' screen; 44 | @import "/css/style4.css" screen, print; 45 | @import url(/css/style5.css); 46 | @import url('/css/style6.css'); 47 | @import url("/css/style7.css"); 48 | @import url( '/css/style8.css' ); 49 | @import url( "/css/style9.css" ); 50 | @import url("/css/style10.css") screen ; 51 | @import url( '/css/style11.css') print, screen; 52 | .bg1 { background: url(/images/theme/bg1.png) } 53 | .bg2 { background: url("/css/images/bg2.png") } 54 | .bg3 { background: url( /images/bg3.png ) } 55 | .bg4 { background: url( '/css/bg4.png' ) } 56 | .bg5 { background: url(http://google.com/images/bg5.png) } 57 | .bg6 { background: url(https://google.com/images/bg5.png) } 58 | .bg7 { background: url(####BULL) } 59 | .bg8 { background: url(data:font/opentype;base64,ABCDEF123456789ABCDEF123456789) } 60 | .bg9 { background: url(//mydomain.com/protocol-relative-url) } 61 | """) 62 | def expectedLinks = [ 63 | '/css/style1.css', 64 | '/css/style2.css', 65 | '/css/style3.css', 66 | '/css/style4.css', 67 | '/css/style5.css', 68 | '/css/style6.css', 69 | '/css/style7.css', 70 | '/css/style8.css', 71 | '/css/style9.css', 72 | '/css/style10.css', 73 | '/css/style11.css', 74 | '/images/theme/bg1.png', 75 | '/css/images/bg2.png', 76 | '/images/bg3.png', 77 | '/css/bg4.png', 78 | 'http://google.com/images/bg5.png', 79 | 'https://google.com/images/bg5.png', 80 | '####BULL', 81 | 'data:font/opentype;base64,ABCDEF123456789ABCDEF123456789', 82 | '//mydomain.com/protocol-relative-url' 83 | ] 84 | def cursor = 0 85 | 86 | def processor = new CSSLinkProcessor() 87 | processor.process(res, mockResSvc) { prefix, original, suffix -> 88 | assertEquals expectedLinks[cursor++], original 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/PathMatcherTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import org.springframework.util.AntPathMatcher 4 | import grails.test.mixin.TestMixin 5 | import grails.test.mixin.support.GrailsUnitTestMixin 6 | 7 | @TestMixin(GrailsUnitTestMixin) 8 | class PathMatcherTests { 9 | static final PATH_MATCHER = new AntPathMatcher() 10 | 11 | void testDeepMatching() { 12 | assertTrue PATH_MATCHER.match('**/.svn', 'web-app/images/.svn') 13 | assertFalse PATH_MATCHER.match('**/.svn', 'web-app/images/.svn/test.jpg') 14 | assertTrue PATH_MATCHER.match('**/.svn/**/*.jpg', 'web-app/images/.svn/test.jpg') 15 | assertTrue PATH_MATCHER.match('**/.svn/**/*.jpg', 'web-app/images/.svn/images/logos/test.jpg') 16 | assertFalse PATH_MATCHER.match('**/.svn/**/*.jpg', 'web-app/images/.svn/images/logos/test.png') 17 | assertTrue PATH_MATCHER.match('**/.svn/**/*.*', 'web-app/images/.svn/images/logos/test.png') 18 | assertTrue PATH_MATCHER.match('**/.svn/**/*.*', 'web-app/images/.svn/css/test.css') 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ProcessingFilterTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.* 4 | import javax.servlet.FilterChain 5 | 6 | import org.springframework.mock.web.MockHttpServletResponse 7 | import org.springframework.mock.web.MockHttpServletRequest 8 | 9 | class ProcessingFilterTests { 10 | @org.junit.Test 11 | void testResourceIsNotProcessedByBothFiltersIfHandledByFirst() { 12 | def filter = new ProcessingFilter() 13 | filter.adhoc = false 14 | filter.grailsResourceProcessor = [ 15 | isDebugMode: { req -> false }, 16 | processModernResource: { req, resp -> resp.committed = true } 17 | ] 18 | 19 | def rq = new MockHttpServletRequest() 20 | def rp = new MockHttpServletResponse() 21 | 22 | def fakeChain = [ 23 | doFilter: { req, resp -> fail('Second filter instance was called') } 24 | ] as FilterChain 25 | 26 | filter.doFilter(rq, rp, fakeChain) 27 | } 28 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceMapperTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | import org.grails.plugin.resource.mapper.ResourceMapper 7 | 8 | @TestMixin(GrailsUnitTestMixin) 9 | class ResourceMapperTests { 10 | void testDefaultIncludesExcludes() { 11 | def artefact = new DummyMapper() 12 | artefact.defaultExcludes = ['**/*.jpg', '**/*.png'] 13 | artefact.defaultIncludes = ['**/*.*'] 14 | artefact.name = 'foobar' 15 | artefact.map = { res, config -> 16 | } 17 | 18 | def m = new ResourceMapper(artefact, [foobar:[:]]) 19 | 20 | def testMeta = new ResourceMeta() 21 | testMeta.sourceUrl = '/images/test.png' 22 | testMeta.actualUrl = '/images/test.png' 23 | testMeta.contentType = "image/png" 24 | 25 | assertFalse m.invokeIfNotExcluded(testMeta) 26 | 27 | def testMetaB = new ResourceMeta() 28 | testMetaB.sourceUrl = '/images/test.jpg' 29 | testMetaB.actualUrl = '/images/test.jpg' 30 | testMetaB.contentType = "image/jpeg" 31 | 32 | assertFalse m.invokeIfNotExcluded(testMetaB) 33 | 34 | def testMeta2 = new ResourceMeta() 35 | testMeta2.sourceUrl = '/images/test.zip' 36 | testMeta2.actualUrl = '/images/test.zip' 37 | testMeta2.contentType = "application/zip" 38 | 39 | assertTrue m.invokeIfNotExcluded(testMeta2) 40 | 41 | } 42 | 43 | void testResourceExclusionOfMapper() { 44 | def artefact = new DummyMapper() 45 | artefact.defaultIncludes = ['**/*.*'] 46 | artefact.name = 'minify' 47 | artefact.map = { res, config -> 48 | } 49 | 50 | def m = new ResourceMapper(artefact, [minify:[:]]) 51 | 52 | def artefact2 = new DummyMapper() 53 | artefact2.defaultIncludes = ['**/*.*'] 54 | artefact2.name = 'other' 55 | artefact2.map = { res, config -> 56 | } 57 | 58 | def m2 = new ResourceMapper(artefact2, [other:[:]]) 59 | 60 | def testMeta = new ResourceMeta() 61 | testMeta.sourceUrl = '/images/test.png' 62 | testMeta.actualUrl = '/images/test.png' 63 | testMeta.contentType = "image/png" 64 | testMeta.excludedMappers = ['minify'] as Set 65 | 66 | assertFalse m.invokeIfNotExcluded(testMeta) 67 | assertTrue m2.invokeIfNotExcluded(testMeta) 68 | } 69 | 70 | void testResourceExclusionOfOperation() { 71 | def artefact = new DummyMapper() 72 | artefact.defaultIncludes = ['**/*.*'] 73 | artefact.name = 'yuicssminifier' 74 | artefact.operation = 'minify' 75 | artefact.map = { res, config -> 76 | } 77 | 78 | def m = new ResourceMapper(artefact, [minify:[:]]) 79 | 80 | def artefact2 = new DummyMapper() 81 | artefact2.defaultIncludes = ['**/*.*'] 82 | artefact2.name = 'googlecssminifier' 83 | artefact2.operation = 'minify' 84 | artefact2.map = { res, config -> 85 | } 86 | 87 | def m2 = new ResourceMapper(artefact2, [other:[:]]) 88 | 89 | def testMeta = new ResourceMeta() 90 | testMeta.sourceUrl = '/images/test.css' 91 | testMeta.actualUrl = '/images/test.css' 92 | testMeta.contentType = "text/css" 93 | testMeta.excludedMappers = ['minify'] as Set 94 | 95 | assertFalse m.invokeIfNotExcluded(testMeta) 96 | assertFalse m2.invokeIfNotExcluded(testMeta) 97 | 98 | testMeta.excludedMappers = null 99 | assertTrue m.invokeIfNotExcluded(testMeta) 100 | assertTrue m2.invokeIfNotExcluded(testMeta) 101 | } 102 | } 103 | 104 | class DummyMapper { 105 | def defaultExcludes 106 | def defaultIncludes 107 | def name 108 | def map 109 | def operation 110 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceMetaStoreTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | import org.grails.plugin.resource.util.ResourceMetaStore 7 | 8 | @TestMixin(GrailsUnitTestMixin) 9 | class ResourceMetaStoreTests { 10 | void testAddingDeclaredResourceAddsBothProcessedAndSourceUrls() { 11 | def r = new ResourceMeta() 12 | r.sourceUrl = "/jquery/images/bg.png" 13 | r.workDir = new File('/tmp/test') 14 | r.processedFile = new File('/tmp/test/123456789.png') 15 | r.updateActualUrlFromProcessedFile() 16 | 17 | def store = new ResourceMetaStore() 18 | store.addDeclaredResource( { 19 | r.actualUrl = "/jquery/images/_bg.png" 20 | return r 21 | } ) 22 | 23 | assertEquals ResourceMetaStore.CLOSED_LATCH, store.latches["/jquery/images/bg.png"] 24 | assertEquals ResourceMetaStore.CLOSED_LATCH, store.latches["/jquery/images/_bg.png"] 25 | } 26 | 27 | void testRequestingResourceThatDoesNotExist() { 28 | def store = new ResourceMetaStore() 29 | def resURI = '/images/idonotexist.jpg' 30 | def res = store.getOrCreateAdHocResource(resURI, { throw new FileNotFoundException('Where my file?') } ) 31 | assertNull "Resource should not have existed", res 32 | 33 | assertNull store.latches[resURI] 34 | } 35 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceMetaTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | @TestMixin(GrailsUnitTestMixin) 7 | class ResourceMetaTests { 8 | void testMovingFileUpdatesActualUrlCorrectly() { 9 | def r = new ResourceMeta() 10 | r.sourceUrl = "/jquery/images/bg.png" 11 | r.workDir = new File('/tmp/test') 12 | r.processedFile = new File('/tmp/test/123456789.png') 13 | r.updateActualUrlFromProcessedFile() 14 | 15 | assertEquals "/jquery/images/bg.png", r.sourceUrl 16 | assertEquals "/123456789.png", r.actualUrl 17 | assertEquals "/123456789.png", r.linkUrl 18 | } 19 | 20 | void testRenamingFileUpdatesActualUrlCorrectly() { 21 | def r = new ResourceMeta() 22 | r.sourceUrl = "/jquery/images/bg.png" 23 | r.workDir = new File('/tmp/test') 24 | r.processedFile = new File('/tmp/test/jquery/images/bg.png.gz') 25 | r.updateActualUrlFromProcessedFile() 26 | 27 | // All results must be abs to the work dir, with leading / 28 | assertEquals "/jquery/images/bg.png", r.sourceUrl 29 | assertEquals "/jquery/images/bg.png.gz", r.actualUrl 30 | assertEquals "/jquery/images/bg.png.gz", r.linkUrl 31 | } 32 | 33 | void testCSSURLWithHackyMozillaAnchorCrapStripsAnchor() { 34 | def r = new ResourceMeta() 35 | r.workDir = new File('/tmp/test') 36 | r.sourceUrl = "/jquery/images/bg.png#crackaddicts" 37 | r.processedFile = new File('/tmp/test/jquery/images/bg.png') 38 | r.updateActualUrlFromProcessedFile() 39 | 40 | // All results must be abs to the work dir, with leading / 41 | assertEquals "Source url should have anchor stripped from it", "/jquery/images/bg.png", r.sourceUrl 42 | assertEquals "/jquery/images/bg.png", r.actualUrl 43 | assertEquals "#crackaddicts", r.sourceUrlParamsAndFragment 44 | assertEquals "/jquery/images/bg.png#crackaddicts", r.linkUrl 45 | } 46 | 47 | void testCSSURLWithAnchorAndQueryParamsMaintained() { 48 | def r = new ResourceMeta() 49 | r.workDir = new File('/tmp/test') 50 | r.sourceUrl = "/jquery/images/bg.png?you=got&to=be&kidding=true#crackaddicts" 51 | r.processedFile = new File('/tmp/test/jquery/images/bg.png') 52 | r.updateActualUrlFromProcessedFile() 53 | 54 | // All results must be abs to the work dir, with leading / 55 | assertEquals "Source url should have anchor stripped from it", "/jquery/images/bg.png", r.sourceUrl 56 | assertEquals "/jquery/images/bg.png", r.actualUrl 57 | assertEquals "?you=got&to=be&kidding=true#crackaddicts", r.sourceUrlParamsAndFragment 58 | assertEquals "/jquery/images/bg.png?you=got&to=be&kidding=true#crackaddicts", r.linkUrl 59 | } 60 | 61 | void testAbsoluteURLWithAnchorAndQueryParamsMaintained() { 62 | def r = new ResourceMeta() 63 | r.workDir = new File('/tmp/test') 64 | r.sourceUrl = "http://crackhouse.ck/jquery/images/bg.png?you=got&to=be&kidding=true#crackaddicts" 65 | r.updateActualUrlFromProcessedFile() 66 | 67 | // All results must be abs to the work dir, with leading / 68 | assertEquals "Source url should have anchor stripped from it", "http://crackhouse.ck/jquery/images/bg.png", r.sourceUrl 69 | assertEquals "http://crackhouse.ck/jquery/images/bg.png", r.actualUrl 70 | assertEquals "?you=got&to=be&kidding=true#crackaddicts", r.sourceUrlParamsAndFragment 71 | assertEquals "http://crackhouse.ck/jquery/images/bg.png?you=got&to=be&kidding=true#crackaddicts", r.linkUrl 72 | } 73 | 74 | void testRelativePathCalculations() { 75 | def data = [ 76 | // Expected, base, target 77 | ["../images/logo.png", '/css/main.css', '/images/logo.png'], 78 | ["../logo.png", '/css/main.css', '/logo.png'], 79 | [ 'images/ui-bg_fine-grain_10_eceadf_60x60.png', '/css/xx/jquery-ui-1.8.16.custom.css', "/css/xx/images/ui-bg_fine-grain_10_eceadf_60x60.png"], 80 | ["_yyyyyy.png", '/_xxxxxx.css', '/_yyyyyy.png'], 81 | ["notgonnahappen/_yyyyyy.png", '/_xxxxxx.css', '/notgonnahappen/_yyyyyy.png'], 82 | ["../notgonnahappen/really/_yyyyyy.png", '/css/_xxxxxx.css', '/notgonnahappen/really/_yyyyyy.png'], 83 | ["../../notgonnahappen/really/_yyyyyy.png", '/css/deep/_xxxxxx.css', '/notgonnahappen/really/_yyyyyy.png'], 84 | ["../../_yyyyyy.png", '/css/deep/_xxxxxx.css', '/_yyyyyy.png'], 85 | ["_xxxx.png", '/css/_zzzzz.css', '/css/_xxxx.png'], 86 | ["../css/_xxxx.png", '/css2/_zzzzz.css', '/css/_xxxx.png'], 87 | ["../css2/_xxxx.png", '/css/_zzzzz.css', '/css2/_xxxx.png'] 88 | ] 89 | 90 | data.each { d -> 91 | println "Trying: ${d}" 92 | def r = new ResourceMeta(actualUrl:d[2]) 93 | assertEquals d[0], r.relativeTo(new ResourceMeta(actualUrl:d[1]) ) 94 | } 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceModuleTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | import org.grails.plugin.resource.module.* 7 | 8 | @TestMixin(GrailsUnitTestMixin) 9 | class ResourceModuleTests { 10 | def svc 11 | 12 | @org.junit.Before 13 | void setupTest() { 14 | svc = new Expando() 15 | svc.getDefaultSettingsForURI = { uri, type -> 16 | [:] 17 | } 18 | } 19 | 20 | void testDefaultBundleFalse() { 21 | def resources = [ 22 | [url:'simile/simile.css'], 23 | [url:'simile/simile.js'] 24 | ] 25 | 26 | def m = new ResourceModule('testModule', resources, false, svc) 27 | 28 | assertEquals 2, m.resources.size() 29 | assertTrue m.resources.every { it.bundle == null } 30 | } 31 | 32 | void testDefaultBundling() { 33 | def resources = [ 34 | [url:'simile/simile.css', disposition:'head'], 35 | [url:'simile/simile.js', disposition:'head'] 36 | ] 37 | 38 | def m = new ResourceModule('testModule', resources, null, svc) 39 | 40 | assertEquals 2, m.resources.size() 41 | m.resources.each { r -> 42 | assertEquals 'bundle_testModule_head', r.bundle 43 | } 44 | } 45 | 46 | void testDefaultBundleWithName() { 47 | def resources = [ 48 | [url:'simile/simile.css', disposition:'defer'], 49 | [url:'simile/simile.js', disposition:'defer'] 50 | ] 51 | 52 | def m = new ResourceModule('testModule', resources, "frank-and-beans", svc) 53 | 54 | assertEquals 2, m.resources.size() 55 | m.resources.each { r -> 56 | assertEquals 'frank-and-beans_defer', r.bundle 57 | } 58 | } 59 | 60 | void testExcludedMapperString() { 61 | def resources = [ 62 | [url:'simile/simile.js', disposition:'head', exclude:'minify'] 63 | ] 64 | 65 | def m = new ResourceModule('testModule', resources, null, svc) 66 | 67 | assertEquals 1, m.resources.size() 68 | assertTrue m.resources[0].excludedMappers.contains('minify') 69 | } 70 | 71 | void testExcludedMapperSet() { 72 | def resources = [ 73 | [url:'simile/simile.js', disposition:'head', exclude:['minify']] 74 | ] 75 | 76 | def m = new ResourceModule('testModule', resources, null, svc) 77 | 78 | assertEquals 1, m.resources.size() 79 | assertTrue m.resources[0].excludedMappers.contains('minify') 80 | } 81 | 82 | void testStringOnlyResource() { 83 | def resources = [ 84 | 'js/test.js' 85 | ] 86 | 87 | def m = new ResourceModule('testModule', resources, null, svc) 88 | 89 | assertEquals 1, m.resources.size() 90 | assertEquals "/js/test.js", m.resources[0].sourceUrl 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceModulesBuilderTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | import org.grails.plugin.resource.module.* 7 | 8 | @TestMixin(GrailsUnitTestMixin) 9 | class ResourceModulesBuilderTests { 10 | def svc 11 | 12 | @org.junit.Before 13 | void setupTest() { 14 | 15 | svc = new Expando() 16 | svc.getDefaultSettingsForURI = { uri, type -> 17 | [:] 18 | } 19 | } 20 | 21 | void testModuleOverrides() { 22 | def modules = [] 23 | def bld = new ModulesBuilder(modules) 24 | 25 | bld.'jquery' { 26 | } 27 | 28 | bld.'horn-smutils' { 29 | dependsOn(['jquery']) 30 | } 31 | 32 | bld.horn { 33 | defaultBundle false 34 | dependsOn(['horn-smutils', 'jquery']) 35 | } 36 | 37 | // knock out the smutils dep and replace 38 | bld.'smutils' { 39 | dependsOn(['jquery']) 40 | } 41 | 42 | bld.overrides { 43 | horn { 44 | defaultBundle true 45 | dependsOn(['smutils', 'jquery']) 46 | } 47 | } 48 | 49 | assert 4 == modules.size() 50 | assert 1 == bld._moduleOverrides.size() 51 | assert 'horn' == bld._moduleOverrides[0].name 52 | assert true == bld._moduleOverrides[0].defaultBundle 53 | assert ['smutils', 'jquery'] == bld._moduleOverrides[0].dependencies 54 | } 55 | 56 | void testModuleDependsOnSyntaxes() { 57 | def modules = [] 58 | def bld = new ModulesBuilder(modules) 59 | 60 | bld.'moduleA' { 61 | dependsOn(['jquery', 'jquery-ui']) 62 | } 63 | 64 | bld.'moduleB' { 65 | dependsOn 'jquery, jquery-ui' 66 | } 67 | 68 | bld.'moduleC' { 69 | dependsOn(['jquery', 'jquery-ui'] as String[]) 70 | } 71 | 72 | shouldFail { 73 | bld.'moduleD' { 74 | // This is bad groovy syntaxt, parens are needed, translates to getProperty('dependsOn') 75 | dependsOn ['jquery', 'jquery-ui'] 76 | } 77 | } 78 | 79 | assert 3 == modules.size() 80 | assert ['jquery', 'jquery-ui'] == modules.find({it.name == 'moduleA'}).dependencies 81 | assert ['jquery', 'jquery-ui'] == modules.find({it.name == 'moduleB'}).dependencies 82 | assert ['jquery', 'jquery-ui'] == modules.find({it.name == 'moduleC'}).dependencies 83 | } 84 | 85 | void testDefaultBundleFalse() { 86 | def modules = [] 87 | def bld = new ModulesBuilder(modules) 88 | 89 | bld.testModule { 90 | defaultBundle false 91 | resource url:'simile/simile.css' 92 | resource url:'simile/simile.js' 93 | } 94 | 95 | assertEquals 1, modules.size() 96 | assertEquals 'testModule', modules[0].name 97 | assertEquals false, modules[0].defaultBundle 98 | } 99 | 100 | void testDefaultBundling() { 101 | def modules = [] 102 | def bld = new ModulesBuilder(modules) 103 | 104 | bld.testModule { 105 | resource url:'simile/simile.css' 106 | resource url:'simile/simile.js' 107 | } 108 | 109 | assertEquals 1, modules.size() 110 | assertEquals 'testModule', modules[0].name 111 | assertNull modules[0].defaultBundle 112 | } 113 | 114 | void testDefaultBundleWithName() { 115 | def modules = [] 116 | def bld = new ModulesBuilder(modules) 117 | 118 | bld.testModule { 119 | defaultBundle "frank-and-beans" 120 | resource url:'simile/simile.css' 121 | resource url:'simile/simile.js' 122 | } 123 | 124 | assertEquals 1, modules.size() 125 | assertEquals 'testModule', modules[0].name 126 | assertEquals 'frank-and-beans', modules[0].defaultBundle 127 | } 128 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/ResourceProcessorSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import org.junit.Rule 4 | import org.junit.rules.TemporaryFolder 5 | import org.springframework.mock.web.MockHttpServletRequest 6 | import org.springframework.mock.web.MockHttpServletResponse 7 | import org.springframework.mock.web.MockServletContext; 8 | 9 | import spock.lang.Specification 10 | import spock.lang.Unroll 11 | 12 | class ResourceProcessorSpec extends Specification { 13 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder() 14 | ResourceProcessor resourceProcessor 15 | MockHttpServletRequest request 16 | MockHttpServletResponse response 17 | ConfigObject config 18 | 19 | def setup() { 20 | resourceProcessor = new ResourceProcessor() 21 | resourceProcessor.staticUrlPrefix = 'static' 22 | config = new ConfigObject() 23 | File temporarySubfolder = temporaryFolder.newFolder('test-tmp') 24 | config.grails.resources.work.dir = temporarySubfolder.getAbsolutePath() 25 | def servletContext = new MockServletContext() 26 | resourceProcessor.grailsApplication = [ 27 | config : config, 28 | mainContext : [servletContext: servletContext] 29 | ] 30 | resourceProcessor.servletContext = servletContext 31 | resourceProcessor.afterPropertiesSet() 32 | request = new MockHttpServletRequest(servletContext) 33 | request.contextPath = '/' 34 | response = new MockHttpServletResponse() 35 | } 36 | 37 | @Unroll 38 | def "check default adhoc.includes/excludes settings - access to url #url should be #accepted"() { 39 | given: 40 | request.requestURI = url 41 | when: 'default adhoc.includes and adhoc.excludes are used' 42 | def uri = null 43 | def exception=null 44 | def result=false 45 | try { 46 | uri=resourceProcessor.removeQueryParams(resourceProcessor.extractURI(request, false)) 47 | result=resourceProcessor.canProcessLegacyResource(uri) 48 | } catch (Exception e) { 49 | result=false 50 | exception=e 51 | } 52 | then: 53 | result == accepted 54 | when: 'adhoc.includes setting is accepting all resources' 55 | resourceProcessor.adHocIncludes = ['/**/*'] 56 | try { 57 | result=resourceProcessor.canProcessLegacyResource(uri) 58 | } catch (Exception e) { 59 | result=false 60 | exception=e 61 | } 62 | then: 63 | result == accepted 64 | where: 65 | url | accepted 66 | '/static/images/image.png' | true 67 | '/static/js/file.js' | true 68 | '/static/WEB-INF/web.xml' | false 69 | '/static/wEb-iNf/web.xml' | false 70 | '/static/web-inf/web.xml' | false 71 | '/static/META-INF/MANIFEST.MF' | false 72 | '/static/meta-inf/MANIFEST.MF' | false 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/URLUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | 2 | package org.grails.plugin.resource 3 | 4 | import spock.lang.Specification 5 | import spock.lang.Unroll; 6 | import static URLUtils.normalizeUri 7 | 8 | class URLUtilsSpec extends Specification { 9 | def './ should get normalized'() { 10 | expect: 11 | normalizeUri('/parentdir/./some-dir/file.xml') == '/parentdir/some-dir/file.xml' 12 | } 13 | 14 | def '../ should get normalized'() { 15 | expect: 16 | normalizeUri('/parentdir/something/../some-dir/file.xml') == '/parentdir/some-dir/file.xml' 17 | } 18 | 19 | def 'fail if ../ goes beyond root'() { 20 | when: 21 | normalizeUri('../../test') 22 | then: 23 | thrown IllegalArgumentException 24 | } 25 | 26 | def 'allow spaces in path'() { 27 | expect: 28 | normalizeUri('/parentdir/a b c.xml') == '/parentdir/a b c.xml' 29 | normalizeUri('/parentdir/a%20b%20c.xml') == '/parentdir/a b c.xml' 30 | } 31 | 32 | def 'fail if contains .. path traversal after decoding'() { 33 | when: 34 | normalizeUri('/some/path/%2e%2e/some-dir/file.xml') 35 | then: 36 | thrown IllegalArgumentException 37 | } 38 | 39 | def 'fail if contains backslash after decoding'() { 40 | when: 41 | normalizeUri('/some/path/%2e%2e%5c%2e%2e/some-dir/file.xml') 42 | then: 43 | thrown IllegalArgumentException 44 | } 45 | 46 | def 'fail if contains . path traversal after decoding'() { 47 | when: 48 | normalizeUri('/some/path/%2e/some-dir/file.xml') 49 | then: 50 | thrown IllegalArgumentException 51 | } 52 | 53 | @Unroll 54 | def 'fail if contains double encoded path traversal going beyond root - #uri'() { 55 | when: 56 | normalizeUri(uri) 57 | then: 58 | thrown IllegalArgumentException 59 | where: 60 | uri|_ 61 | '/static/css/..%252f..%252f..%252fsecrets.txt'|_ 62 | '/static/css/some..%252fa..%252fb..%252fsecrets.txt'|_ 63 | '/static/css/..a%252f..b%252f..c%252fsecrets.txt'|_ 64 | } 65 | 66 | def 'double url encoded should get normalized'() { 67 | expect: 68 | normalizeUri('/parentdir/%25%37%33%25%36%66%25%36%64%25%36%35%25%32%64%25%36%34%25%36%39%25%37%32/file.xml') == '/parentdir/some-dir/file.xml' 69 | } 70 | 71 | def 'triple url encoded should get normalized'() { 72 | expect: 73 | normalizeUri('/parentdir/%25%32%35%25%33%37%25%33%33%25%32%35%25%33%36%25%36%36%25%32%35%25%33%36%25%36%34%25%32%35%25%33%36%25%33%35%25%32%35%25%33%32%25%36%34%25%32%35%25%33%36%25%33%34%25%32%35%25%33%36%25%33%39%25%32%35%25%33%37%25%33%32/file.xml') == '/parentdir/some-dir/file.xml' 74 | } 75 | 76 | def 'fail if normalization limit exceeds'() { 77 | when: 78 | def uri=normalizeUri('/parentdir/%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%37%25%32%35%25%33%33%25%33%33%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%36%25%32%35%25%33%36%25%33%36%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%36%25%32%35%25%33%36%25%33%34%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%36%25%32%35%25%33%33%25%33%35%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%32%25%32%35%25%33%36%25%33%34%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%36%25%32%35%25%33%33%25%33%34%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%36%25%32%35%25%33%33%25%33%39%25%32%35%25%33%32%25%33%35%25%32%35%25%33%33%25%33%37%25%32%35%25%33%33%25%33%32/file.xml') 79 | then: 80 | thrown IllegalArgumentException 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/URLUtilsTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | @TestMixin(GrailsUnitTestMixin) 7 | class URLUtilsTests { 8 | 9 | void testRelativeCSSUris() { 10 | assertEquals "images/bg_fade.png", URLUtils.relativeURI('css/main.css', '../images/bg_fade.png') 11 | assertEquals "/images/bg_fade.png", URLUtils.relativeURI('/css/main.css', '../images/bg_fade.png') 12 | assertEquals "/css/images/bg_fade.png", URLUtils.relativeURI('/css/main.css', './images/bg_fade.png') 13 | assertEquals "css/images/bg_fade.png", URLUtils.relativeURI('css/main.css', './images/bg_fade.png') 14 | assertEquals "bg_fade.png", URLUtils.relativeURI('main.css', 'bg_fade.png') 15 | assertEquals "/bg_fade.png", URLUtils.relativeURI('/main.css', 'bg_fade.png') 16 | assertEquals "css/bg_fade.png", URLUtils.relativeURI('css/main.css', 'bg_fade.png') 17 | assertEquals "/css/bg_fade.png", URLUtils.relativeURI('/css/main.css', 'bg_fade.png') 18 | assertEquals "/bg_fade.png", URLUtils.relativeURI('/main.css', '/bg_fade.png') 19 | assertEquals "/bg_fade.png", URLUtils.relativeURI('css/main.css', '/bg_fade.png') 20 | assertEquals "/bg_fade.png", URLUtils.relativeURI('/css/main.css', '/bg_fade.png') 21 | assertEquals "http://somewhere.com/images/x.png", URLUtils.relativeURI('css/main.css', 'http://somewhere.com/images/x.png') 22 | } 23 | 24 | void testIsRelativeForServerRelativeUrls() { 25 | assertTrue URLUtils.isRelativeURL("/server/relative") 26 | } 27 | 28 | void testIsRelativeForRelativeToCurrentPath() { 29 | assertTrue URLUtils.isRelativeURL("relative/to/current/path") 30 | } 31 | 32 | void testIsRelativeForRelativeToCurrentPathViaParent() { 33 | assertTrue URLUtils.isRelativeURL("../relative/to/current/path") 34 | } 35 | 36 | void testIsRelativeForDataUrls() { 37 | assertFalse URLUtils.isRelativeURL("data:xyz") 38 | } 39 | 40 | void testIsRelativeForPageFragments() { 41 | assertFalse URLUtils.isRelativeURL("#fragment_only") 42 | } 43 | 44 | void testIsRelativeForAbsoluteUrls() { 45 | assertFalse URLUtils.isRelativeURL("http://www.example.org/absolute/path") 46 | } 47 | 48 | void testIsExternalUrl() { 49 | assertTrue URLUtils.isExternalURL('http://images.examples.com') 50 | assertTrue URLUtils.isExternalURL('https://images.examples.com') 51 | assertTrue URLUtils.isExternalURL('//images.examples.com') 52 | 53 | assertFalse URLUtils.isExternalURL('/images/exapmles.com') 54 | assertFalse URLUtils.isExternalURL('://images.examples.com') 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resource/util/DispositionsUtilsTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resource.util 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | 6 | 7 | @TestMixin(GrailsUnitTestMixin) 8 | class DispositionsUtilsTests { 9 | 10 | void testAddingDispositionToRequest() { 11 | def request = [:] 12 | assertTrue DispositionsUtils.getRequestDispositionsRemaining(request).empty 13 | 14 | DispositionsUtils.addDispositionToRequest(request, 'head', 'dummy') 15 | assertTrue((['head'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 16 | 17 | // Let's just make sure its a set 18 | DispositionsUtils.addDispositionToRequest(request, 'head', 'dummy') 19 | assertTrue((['head'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 20 | 21 | DispositionsUtils.addDispositionToRequest(request, 'defer', 'dummy') 22 | assertTrue((['head', 'defer'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 23 | 24 | DispositionsUtils.addDispositionToRequest(request, 'image', 'dummy') 25 | assertTrue((['head', 'image', 'defer'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 26 | } 27 | 28 | void testRemovingDispositionFromRequest() { 29 | def request = [(DispositionsUtils.REQ_ATTR_DISPOSITIONS_REMAINING):(['head', 'image', 'defer'] as Set)] 30 | 31 | assertTrue((['head', 'image', 'defer'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 32 | 33 | DispositionsUtils.removeDispositionFromRequest(request, 'head') 34 | assertTrue((['defer', 'image'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 35 | 36 | DispositionsUtils.removeDispositionFromRequest(request, 'defer') 37 | assertTrue((['image'] as Set) == DispositionsUtils.getRequestDispositionsRemaining(request)) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resources/stash/ScriptStashWriterUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resources.stash 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | import org.junit.Before 6 | import org.junit.Test 7 | 8 | /** 9 | * Unit tests for {@link ScriptStashWriter}. 10 | * 11 | * @author Patrick Jungermann 12 | */ 13 | @TestMixin(GrailsUnitTestMixin) 14 | class ScriptStashWriterUnitTests { 15 | 16 | ScriptStashWriter writer 17 | 18 | @Before 19 | void setUp() { 20 | writer = new ScriptStashWriter() 21 | } 22 | 23 | @Test(expected = NullPointerException) 24 | void writeButNoOutputTarget() { 25 | writer.write(null, ["fragment"]) 26 | } 27 | 28 | @Test(expected = NullPointerException) 29 | void writeButNoStash() { 30 | writer.write(new StringWriter(), null) 31 | } 32 | 33 | @Test 34 | void writeEmptyStash() { 35 | StringWriter out = new StringWriter() 36 | 37 | writer.write(out, []) 38 | 39 | assertEquals "", out.toString() 40 | } 41 | 42 | @Test 43 | void writeStashWithOneFragment() { 44 | StringWriter out = new StringWriter() 45 | 46 | writer.write(out, ["fragment;"]) 47 | 48 | String expected = "" 49 | assertEquals expected, out.toString() 50 | } 51 | 52 | @Test 53 | void writeStashWithMultipleFragments() { 54 | StringWriter out = new StringWriter() 55 | 56 | writer.write(out, ["fragment1;", "fragment2;"]) 57 | 58 | String expected = "" 59 | assertEquals expected, out.toString() 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /test/unit/org/grails/plugin/resources/stash/StyleStashWriterUnitTests.groovy: -------------------------------------------------------------------------------- 1 | package org.grails.plugin.resources.stash 2 | 3 | import grails.test.mixin.TestMixin 4 | import grails.test.mixin.support.GrailsUnitTestMixin 5 | import org.junit.Before 6 | import org.junit.Test 7 | 8 | /** 9 | * Unit tests for {@link StyleStashWriter}. 10 | * 11 | * @author Patrick Jungermann 12 | */ 13 | @TestMixin(GrailsUnitTestMixin) 14 | class StyleStashWriterUnitTests { 15 | 16 | StyleStashWriter writer 17 | 18 | @Before 19 | void setUp() { 20 | writer = new StyleStashWriter() 21 | } 22 | 23 | @Test(expected = NullPointerException) 24 | void writeButNoOutputTarget() { 25 | writer.write(null, ["fragment"]) 26 | } 27 | 28 | @Test(expected = NullPointerException) 29 | void writeButNoStash() { 30 | writer.write(new StringWriter(), null) 31 | } 32 | 33 | @Test 34 | void writeEmptyStash() { 35 | StringWriter out = new StringWriter() 36 | 37 | writer.write(out, []) 38 | 39 | assertEquals "", out.toString() 40 | } 41 | 42 | @Test 43 | void writeStashWithOneFragment() { 44 | StringWriter out = new StringWriter() 45 | 46 | writer.write(out, ["fragment;"]) 47 | 48 | String expected = "" 49 | assertEquals expected, out.toString() 50 | } 51 | 52 | @Test 53 | void writeStashWithMultipleFragments() { 54 | StringWriter out = new StringWriter() 55 | 56 | writer.write(out, ["fragment1;", "fragment2;"]) 57 | 58 | String expected = "" 59 | assertEquals expected, out.toString() 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | rm -rf *.zip 4 | ./grailsw refresh-dependencies --non-interactive 5 | ./grailsw test-app --non-interactive 6 | ./grailsw package-plugin --non-interactive 7 | ./grailsw doc --pdf --non-interactive 8 | 9 | filename=$(find . -name "grails-*.zip" | head -1) 10 | filename=$(basename $filename) 11 | plugin=${filename:7} 12 | plugin=${plugin/.zip/} 13 | plugin=${plugin/-SNAPSHOT/} 14 | version="${plugin#*-}"; 15 | plugin=${plugin/"-$version"/} 16 | 17 | echo "Publishing plugin $plugin with version $version" 18 | 19 | if [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_REPO_SLUG == "grails-plugins/grails-$plugin" && $TRAVIS_PULL_REQUEST == 'false' && $TRAVIS_JOB_NUMBER =~ ^[0-9]+(\.1)?$ ]]; then 20 | git config --global user.name "$GIT_NAME" 21 | git config --global user.email "$GIT_EMAIL" 22 | git config --global credential.helper "store --file=~/.git-credentials" 23 | echo "https://$GH_TOKEN:@github.com" > ~/.git-credentials 24 | 25 | 26 | if [[ $filename != *-SNAPSHOT* ]] 27 | then 28 | git clone https://${GH_TOKEN}@github.com/$TRAVIS_REPO_SLUG.git -b gh-pages gh-pages --single-branch > /dev/null 29 | cd gh-pages 30 | git rm -rf . 31 | cp -r ../target/docs/. ./ 32 | git add * 33 | git commit -a -m "Updating docs for Travis build: https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID" 34 | git push origin HEAD 35 | cd .. 36 | rm -rf gh-pages 37 | else 38 | echo "SNAPSHOT version, not publishing docs" 39 | fi 40 | 41 | 42 | ./grailsw publish-plugin --no-scm --allow-overwrite --non-interactive 43 | else 44 | echo "Not on master branch, so not publishing" 45 | echo "TRAVIS_BRANCH: $TRAVIS_BRANCH" 46 | echo "TRAVIS_REPO_SLUG: $TRAVIS_REPO_SLUG" 47 | echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" 48 | fi -------------------------------------------------------------------------------- /web-app/GPRESOURCES-207/file1.js: -------------------------------------------------------------------------------- 1 | // empty file 2 | -------------------------------------------------------------------------------- /web-app/GPRESOURCES-207/file2.js: -------------------------------------------------------------------------------- 1 | // empty file 2 | -------------------------------------------------------------------------------- /web-app/GPRESOURCES-207/file3.js: -------------------------------------------------------------------------------- 1 | // empty file 2 | -------------------------------------------------------------------------------- /web-app/GPRESOURCES-207/file4.js: -------------------------------------------------------------------------------- 1 | // empty file 2 | -------------------------------------------------------------------------------- /web-app/GPRESOURCES-207/file5.js: -------------------------------------------------------------------------------- 1 | // empty file 2 | -------------------------------------------------------------------------------- /web-app/GPRESOURCES-210/file1.js: -------------------------------------------------------------------------------- 1 | // comment -------------------------------------------------------------------------------- /web-app/GPRESOURCES-210/file2.js: -------------------------------------------------------------------------------- 1 | // comment -------------------------------------------------------------------------------- /web-app/WEB-INF/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Grails application factory bean 9 | 10 | 11 | 12 | 13 | 14 | A bean that manages Grails plugins 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | utf-8 31 | 32 | 33 | -------------------------------------------------------------------------------- /web-app/WEB-INF/sitemesh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web-app/css/blueprint/ie.css: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------- 2 | 3 | 4 | Blueprint CSS Framework 0.9 5 | http://blueprintcss.org 6 | 7 | * Copyright (c) 2007-Present. See LICENSE for more info. 8 | * See README for instructions on how to use Blueprint. 9 | * For credits and origins, see AUTHORS. 10 | * This is a compressed file. See the sources in the 'src' directory. 11 | 12 | ----------------------------------------------------------------------- */ 13 | 14 | /* ie.css */ 15 | body {text-align:center;} 16 | .container {text-align:left;} 17 | * html .column, * html div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.span-24 {overflow-x:hidden;} 18 | * html legend {margin:0px -8px 16px 0;padding:0;} 19 | ol {margin-left:2em;} 20 | sup {vertical-align:text-top;} 21 | sub {vertical-align:text-bottom;} 22 | html>body p code {*white-space:normal;} 23 | hr {margin:-8px auto 11px;} 24 | img {-ms-interpolation-mode:bicubic;} 25 | .clearfix, .container {display:inline-block;} 26 | * html .clearfix, * html .container {height:1%;} 27 | fieldset {padding-top:0;} 28 | input.text, input.title {background-color:#fff;border:1px solid #bbb;} 29 | input.text:focus, input.title:focus {border-color:#666;} 30 | input.text, input.title, textarea, select {margin:0.5em 0;} 31 | input.checkbox, input.radio {position:relative;top:.25em;} 32 | form.inline div, form.inline p {vertical-align:middle;} 33 | form.inline label {position:relative;top:-0.25em;} 34 | form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;} 35 | button, input.button {position:relative;top:0.25em;} -------------------------------------------------------------------------------- /web-app/css/blueprint/src/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/css/blueprint/src/grid.png -------------------------------------------------------------------------------- /web-app/css/errors.css: -------------------------------------------------------------------------------- 1 | h1, h2 { 2 | margin: 10px 25px 5px; 3 | } 4 | 5 | h2 { 6 | font-size: 1.1em; 7 | } 8 | 9 | .filename { 10 | font-style: italic; 11 | } 12 | 13 | .exceptionMessage { 14 | margin: 10px; 15 | border: 1px solid #000; 16 | padding: 5px; 17 | background-color: #E9E9E9; 18 | } 19 | 20 | .stack, 21 | .snippet { 22 | margin: 0 25px 10px; 23 | } 24 | 25 | .stack, 26 | .snippet { 27 | border: 1px solid #ccc; 28 | -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2); 29 | -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2); 30 | box-shadow: 0 0 2px rgba(0,0,0,0.2); 31 | } 32 | 33 | /* error details */ 34 | .error-details { 35 | border-top: 1px solid #FFAAAA; 36 | -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2); 37 | -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2); 38 | box-shadow: 0 0 2px rgba(0,0,0,0.2); 39 | border-bottom: 1px solid #FFAAAA; 40 | -mox-box-shadow: 0 0 2px rgba(0,0,0,0.2); 41 | -webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2); 42 | box-shadow: 0 0 2px rgba(0,0,0,0.2); 43 | background-color:#FFF3F3; 44 | line-height: 1.5; 45 | overflow: hidden; 46 | padding: 5px; 47 | padding-left:25px; 48 | } 49 | 50 | .error-details dt { 51 | clear: left; 52 | float: left; 53 | font-weight: bold; 54 | margin-right: 5px; 55 | } 56 | 57 | .error-details dt:after { 58 | content: ":"; 59 | } 60 | 61 | .error-details dd { 62 | display: block; 63 | } 64 | 65 | /* stack trace */ 66 | .stack { 67 | padding: 5px; 68 | overflow: auto; 69 | height: 150px; 70 | } 71 | 72 | /* code snippet */ 73 | .snippet { 74 | background-color: #fff; 75 | font-family: monospace; 76 | } 77 | 78 | .snippet .line { 79 | display: block; 80 | } 81 | 82 | .snippet .lineNumber { 83 | background-color: #ddd; 84 | color: #999; 85 | display: inline-block; 86 | margin-right: 5px; 87 | padding: 0 3px; 88 | text-align: right; 89 | width: 3em; 90 | } 91 | 92 | .snippet .error { 93 | background-color: #fff3f3; 94 | font-weight: bold; 95 | } 96 | 97 | .snippet .error .lineNumber { 98 | background-color: #faa; 99 | color: #333; 100 | font-weight: bold; 101 | } 102 | 103 | .snippet .line:first-child .lineNumber { 104 | padding-top: 5px; 105 | } 106 | 107 | .snippet .line:last-child .lineNumber { 108 | padding-bottom: 5px; 109 | } -------------------------------------------------------------------------------- /web-app/css/legacy.css: -------------------------------------------------------------------------------- 1 | /* Hello world */ 2 | body { 3 | color: #222; 4 | } 5 | -------------------------------------------------------------------------------- /web-app/css/mobile.css: -------------------------------------------------------------------------------- 1 | /* Styles for mobile devices */ 2 | 3 | @media screen and (max-width: 480px) { 4 | .nav { 5 | padding: 0.5em; 6 | } 7 | 8 | .nav li { 9 | margin: 0 0.5em 0 0; 10 | padding: 0.25em; 11 | } 12 | 13 | /* Hide individual steps in pagination, just have next & previous */ 14 | .pagination .step, .pagination .currentStep { 15 | display: none; 16 | } 17 | 18 | .pagination .prevLink { 19 | float: left; 20 | } 21 | 22 | .pagination .nextLink { 23 | float: right; 24 | } 25 | 26 | /* pagination needs to wrap around floated buttons */ 27 | .pagination { 28 | overflow: hidden; 29 | } 30 | 31 | /* slightly smaller margin around content body */ 32 | fieldset, 33 | .property-list { 34 | padding: 0.3em 1em 1em; 35 | } 36 | 37 | input, textarea { 38 | width: 100%; 39 | -moz-box-sizing: border-box; 40 | -webkit-box-sizing: border-box; 41 | -ms-box-sizing: border-box; 42 | box-sizing: border-box; 43 | } 44 | 45 | select, input[type=checkbox], input[type=radio], input[type=submit], input[type=button], input[type=reset] { 46 | width: auto; 47 | } 48 | 49 | /* hide all but the first column of list tables */ 50 | .scaffold-list td:not(:first-child), 51 | .scaffold-list th:not(:first-child) { 52 | display: none; 53 | } 54 | 55 | .scaffold-list thead th { 56 | text-align: center; 57 | } 58 | 59 | /* stack form elements */ 60 | .fieldcontain { 61 | margin-top: 0.6em; 62 | } 63 | 64 | .fieldcontain label, 65 | .fieldcontain .property-label, 66 | .fieldcontain .property-value { 67 | display: block; 68 | float: none; 69 | margin: 0 0 0.25em 0; 70 | text-align: left; 71 | width: auto; 72 | } 73 | 74 | .errors ul, 75 | .message p { 76 | margin: 0.5em; 77 | } 78 | 79 | .error ul { 80 | margin-left: 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /web-app/images/apple-touch-icon-retina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/apple-touch-icon-retina.png -------------------------------------------------------------------------------- /web-app/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/apple-touch-icon.png -------------------------------------------------------------------------------- /web-app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/favicon.ico -------------------------------------------------------------------------------- /web-app/images/grails_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/grails_logo.jpg -------------------------------------------------------------------------------- /web-app/images/grails_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/grails_logo.png -------------------------------------------------------------------------------- /web-app/images/leftnav_btm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/leftnav_btm.png -------------------------------------------------------------------------------- /web-app/images/leftnav_midstretch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/leftnav_midstretch.png -------------------------------------------------------------------------------- /web-app/images/leftnav_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/leftnav_top.png -------------------------------------------------------------------------------- /web-app/images/skin/database_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/database_add.png -------------------------------------------------------------------------------- /web-app/images/skin/database_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/database_delete.png -------------------------------------------------------------------------------- /web-app/images/skin/database_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/database_edit.png -------------------------------------------------------------------------------- /web-app/images/skin/database_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/database_save.png -------------------------------------------------------------------------------- /web-app/images/skin/database_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/database_table.png -------------------------------------------------------------------------------- /web-app/images/skin/exclamation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/exclamation.png -------------------------------------------------------------------------------- /web-app/images/skin/house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/house.png -------------------------------------------------------------------------------- /web-app/images/skin/information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/information.png -------------------------------------------------------------------------------- /web-app/images/skin/shadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/shadow.jpg -------------------------------------------------------------------------------- /web-app/images/skin/sorted_asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/sorted_asc.gif -------------------------------------------------------------------------------- /web-app/images/skin/sorted_desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/skin/sorted_desc.gif -------------------------------------------------------------------------------- /web-app/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/spinner.gif -------------------------------------------------------------------------------- /web-app/images/springsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/images/springsource.png -------------------------------------------------------------------------------- /web-app/js/adhoc.js: -------------------------------------------------------------------------------- 1 | // Nothing here either -------------------------------------------------------------------------------- /web-app/js/application.js: -------------------------------------------------------------------------------- 1 | var Ajax; 2 | if (Ajax && (Ajax != null)) { 3 | Ajax.Responders.register({ 4 | onCreate: function() { 5 | if($('spinner') && Ajax.activeRequestCount>0) 6 | Effect.Appear('spinner',{duration:0.5,queue:'end'}); 7 | }, 8 | onComplete: function() { 9 | if($('spinner') && Ajax.activeRequestCount==0) 10 | Effect.Fade('spinner',{duration:0.5,queue:'end'}); 11 | } 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /web-app/js/core.js: -------------------------------------------------------------------------------- 1 | // Nothing to see here 2 | -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_flat_15_cd0a0a_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_flat_15_cd0a0a_40x100.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_100_e4f1fb_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_100_e4f1fb_1x400.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_50_3baae3_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_50_3baae3_1x400.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_80_d7ebf9_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_glass_80_d7ebf9_1x400.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-hard_70_000000_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-hard_70_000000_1x100.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-soft_100_deedf7_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-soft_100_deedf7_1x100.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-soft_25_ffef8f_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-bg_highlight-soft_25_ffef8f_1x100.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_2694e8_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_2694e8_256x240.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_3d80b3_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_3d80b3_256x240.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_72a7cf_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_72a7cf_256x240.png -------------------------------------------------------------------------------- /web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/web-app/js/jquery-ui/themes/custom-theme/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /wrapper/grails-wrapper-runtime-2.3.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/wrapper/grails-wrapper-runtime-2.3.11.jar -------------------------------------------------------------------------------- /wrapper/grails-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapper.dist.url=http://dist.springframework.org.s3.amazonaws.com/release/GRAILS/ 2 | -------------------------------------------------------------------------------- /wrapper/springloaded-1.2.0.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-resources/5577fdd43a45ad2c3db0c40854736eaa9f342cf7/wrapper/springloaded-1.2.0.RELEASE.jar --------------------------------------------------------------------------------