diff --git a/grails-test-suite-web/src/test/groovy/org/grails/web/mapping/UrlMappingsHolderTests.groovy b/grails-test-suite-web/src/test/groovy/org/grails/web/mapping/UrlMappingsHolderTests.groovy index 220820cef89..08bba07c2b2 100644 --- a/grails-test-suite-web/src/test/groovy/org/grails/web/mapping/UrlMappingsHolderTests.groovy +++ b/grails-test-suite-web/src/test/groovy/org/grails/web/mapping/UrlMappingsHolderTests.groovy @@ -58,6 +58,28 @@ mappings { } ''' + def mappingWithNamespace = ''' +mappings { + "/$namespace/$controller/$action?/$id?" () +} +''' + + @Test + void testGetReverseMappingWithNamespace() { + def res = new ByteArrayResource(mappingWithNamespace.bytes) + + def evaluator = new DefaultUrlMappingEvaluator(mainContext) + def mappings = evaluator.evaluateMappings(res) + + def holder = new DefaultUrlMappingsHolder(mappings) + + def m = holder.getReverseMapping("product", "show", "foo", null, [:]) + assertNotNull "getReverseMapping returned null", m + + assertEquals "/foo/product/show", m.createURL("product", "show", "foo", null, [:], "utf-8") + } + + @Test void testGetReverseMappingWithNamedArgsAndClosure() { def res = new ByteArrayResource(mappingWithNamedArgsAndClosure.bytes) diff --git a/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/DefaultRequestStateLookupStrategy.java b/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/DefaultRequestStateLookupStrategy.java index 867a0de1abc..312247d0475 100644 --- a/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/DefaultRequestStateLookupStrategy.java +++ b/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/DefaultRequestStateLookupStrategy.java @@ -83,6 +83,14 @@ private String getControllerNameInternal(GrailsWebRequest req) { return null; } + public String getControllerNamespace() { + final GrailsWebRequest req = getWebRequest(); + if (req != null) { + return req.getControllerNamespace(); + } + return null; + } + public String getActionName() { final GrailsWebRequest req = getWebRequest(); if (req != null) { diff --git a/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/GrailsRequestStateLookupStrategy.java b/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/GrailsRequestStateLookupStrategy.java index affb27a53e0..5fd8bc5b9d9 100644 --- a/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/GrailsRequestStateLookupStrategy.java +++ b/grails-web-common/src/main/groovy/org/grails/web/servlet/mvc/GrailsRequestStateLookupStrategy.java @@ -43,6 +43,13 @@ public interface GrailsRequestStateLookupStrategy { */ public String getControllerName(); + /** + * The controller namespace + * + * @return The controller namespace or null if not known + */ + public String getControllerNamespace(); + /** * The action name for the given controller name * diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/CachingLinkGenerator.java b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/CachingLinkGenerator.java index dd962605a50..bccdbb0f725 100644 --- a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/CachingLinkGenerator.java +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/CachingLinkGenerator.java @@ -17,6 +17,7 @@ import java.util.Map; +import grails.util.GrailsStringUtils; import grails.web.mapping.LinkGenerator; import grails.web.mapping.UrlMapping; import grails.util.GrailsMetaClassUtils; @@ -120,6 +121,13 @@ private void appendMapKey(StringBuilder buffer, Map map) { } appendKeyValue(buffer, map, key, value); } + if (map.get(UrlMapping.NAMESPACE) == null) { + String namespace = getRequestStateLookupStrategy().getControllerNamespace(); + if (GrailsStringUtils.isNotEmpty(namespace)) { + appendCommaIfNotFirst(buffer, first); + appendKeyValue(buffer, map, UrlMapping.NAMESPACE, namespace); + } + } buffer.append(CLOSING_BRACKET); } diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultLinkGenerator.groovy b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultLinkGenerator.groovy index df639d598b7..e1784c8b01e 100644 --- a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultLinkGenerator.groovy +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultLinkGenerator.groovy @@ -243,19 +243,19 @@ class DefaultLinkGenerator implements LinkGenerator, org.codehaus.groovy.grails. params.put(ATTRIBUTE_ID, id) } def pluginName = attrs.get(UrlMapping.PLUGIN)?.toString() - def namespace = attrs.get(UrlMapping.NAMESPACE)?.toString() + def namespace = attrs.get(UrlMapping.NAMESPACE)?.toString() ?: requestStateLookupStrategy.controllerNamespace UrlCreator mapping = urlMappingsHolder.getReverseMappingNoDefault(controller,action,namespace,pluginName,httpMethod,params) if (mapping == null && isDefaultAction) { mapping = urlMappingsHolder.getReverseMappingNoDefault(controller,null,namespace,pluginName,httpMethod,params) } if (mapping == null) { - mapping = urlMappingsHolder.getReverseMapping(controller,action,pluginName,httpMethod,params) + mapping = urlMappingsHolder.getReverseMapping(controller,action,namespace,pluginName,httpMethod,params) } boolean absolute = isAbsolute(attrs) if (!absolute) { - url = mapping.createRelativeURL(convertedControllerName, convertedActionName, params, encoding, frag) + url = mapping.createRelativeURL(convertedControllerName, convertedActionName, namespace, pluginName, params, encoding, frag) final contextPathAttribute = attrs.get(ATTRIBUTE_CONTEXT_PATH) final cp = contextPathAttribute == null ? getContextPath() : contextPathAttribute if (attrs.get(ATTRIBUTE_BASE) || cp == null) { @@ -268,7 +268,7 @@ class DefaultLinkGenerator implements LinkGenerator, org.codehaus.groovy.grails. writer.append url } else { - url = mapping.createRelativeURL(convertedControllerName, convertedActionName, params, encoding, frag) + url = mapping.createRelativeURL(convertedControllerName, convertedActionName, namespace, pluginName, params, encoding, frag) writer.append handleAbsolute(attrs) writer.append url } diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java index 8039a8a4a7e..966f80dfdb3 100644 --- a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java @@ -85,6 +85,8 @@ public int weightOf(List values) { private Map mappingsLookup = new HashMap(); private Map namedMappings = new HashMap(); private UrlMappingsList mappingsListLookup = new UrlMappingsList(); + private Set DEFAULT_NAMESPACE_PARAMS = CollectionUtils.newSet( + UrlMapping.NAMESPACE, UrlMapping.CONTROLLER, UrlMapping.ACTION); private Set DEFAULT_CONTROLLER_PARAMS = CollectionUtils.newSet( UrlMapping.CONTROLLER, UrlMapping.ACTION); private Set DEFAULT_ACTION_PARAMS = CollectionUtils.newSet(UrlMapping.ACTION); @@ -356,6 +358,27 @@ private UrlCreator resolveUrlCreator(final String controller, } } } + if (mapping == null || (mapping instanceof ResponseCodeUrlMapping)) { + Set lookupParams = new HashSet(DEFAULT_NAMESPACE_PARAMS); + Set paramKeys = new HashSet(params.keySet()); + paramKeys.removeAll(lookupParams); + lookupParams.addAll(paramKeys); + UrlMappingKey lookupKey = new UrlMappingKey(null, null, null, pluginName, httpMethod, version,lookupParams); + mapping = mappingsLookup.get(lookupKey); + if (mapping == null) { + lookupKey.httpMethod = UrlMapping.ANY_HTTP_METHOD; + mapping = mappingsLookup.get(lookupKey); + } + if (mapping == null) { + lookupParams.removeAll(paramKeys); + UrlMappingKey lookupKeyModifiedParams = new UrlMappingKey(null, null, null, pluginName, httpMethod, version,lookupParams); + mapping = mappingsLookup.get(lookupKeyModifiedParams); + if (mapping == null) { + lookupKeyModifiedParams.httpMethod = UrlMapping.ANY_HTTP_METHOD; + mapping = mappingsLookup.get(lookupKeyModifiedParams); + } + } + } UrlCreator creator = null; if (mapping == null || (mapping instanceof ResponseCodeUrlMapping)) { if (useDefault) { diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/RegexUrlMapping.java b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/RegexUrlMapping.java index 4a5e59e84bf..47d058365a9 100644 --- a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/RegexUrlMapping.java +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/RegexUrlMapping.java @@ -558,6 +558,7 @@ private void populateParameterList(Map paramValues, String encoding, StringBuild usedParams.add("controller"); usedParams.add("action"); usedParams.add("namespace"); + usedParams.add("plugin"); // A 'null' encoding will cause an exception, so default to 'UTF-8'. if (encoding == null) { diff --git a/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/CachingLinkGeneratorSpec.groovy b/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/CachingLinkGeneratorSpec.groovy new file mode 100644 index 00000000000..df6edf233d6 --- /dev/null +++ b/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/CachingLinkGeneratorSpec.groovy @@ -0,0 +1,73 @@ +package org.codehaus.groovy.grails.web.mapping + +import grails.util.GrailsWebMockUtil +import org.grails.web.mapping.CachingLinkGenerator +import org.grails.web.servlet.mvc.DefaultRequestStateLookupStrategy +import org.grails.web.servlet.mvc.GrailsWebRequest +import org.springframework.web.context.request.RequestContextHolder +import spock.lang.Shared +import spock.lang.Specification + +/** + * Tests for the {@link org.grails.web.mapping.CachingLinkGenerator} class + */ +class CachingLinkGeneratorSpec extends Specification { + + @Shared + MyCachingLinkGenerator linkGenerator + + @Shared + GrailsWebRequest request + + void setup() { + linkGenerator = new MyCachingLinkGenerator("http://grails.org/") + request = GrailsWebMockUtil.bindMockWebRequest() + linkGenerator.requestStateLookupStrategy = new DefaultRequestStateLookupStrategy(request) + } + + void cleanup() { + RequestContextHolder.resetRequestAttributes() + } + + void "test namespace"() { + given: + String key + + when: "not in the request or params" + key = linkGenerator.makeKey([controller: "foo", action: "bar"]) + + then: "its not in the key" + key == "link[controller:foo, action:bar]" + + when: "its in the params" + key = linkGenerator.makeKey([controller: "foo", action: "bar", namespace: "foo"]) + + then: "its in the key" + key == "link[controller:foo, action:bar, namespace:foo]" + + when: "its in the request" + request.setControllerNamespace("fooReq") + key = linkGenerator.makeKey([controller: "foo", action: "bar"]) + + then: "its in the key" + key == "link[controller:foo, action:bar, namespace:fooReq]" + + when: "its in the request and the params" + request.setControllerNamespace("fooReq") + key = linkGenerator.makeKey([controller: "foo", action: "bar", namespace: "fooParam"]) + + then: "params wins" + key == "link[controller:foo, action:bar, namespace:fooParam]" + } + + + class MyCachingLinkGenerator extends CachingLinkGenerator { + public MyCachingLinkGenerator(String serverBaseURL) { + super(serverBaseURL) + } + + String makeKey(Map attrs) { + super.makeKey(LINK_PREFIX, attrs) + } + } +} diff --git a/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/LinkGeneratorSpec.groovy b/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/LinkGeneratorSpec.groovy index 62143b4b59b..668724ec8e1 100644 --- a/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/LinkGeneratorSpec.groovy +++ b/grails-web-url-mappings/src/test/groovy/org/codehaus/groovy/grails/web/mapping/LinkGeneratorSpec.groovy @@ -285,7 +285,39 @@ class LinkGeneratorSpec extends Specification { then: cacheKey == "somePrefix[resource:org.codehaus.groovy.grails.web.mapping.Widget->2]" } - + + def 'link should take into affect namespace'() { + given: + final webRequest = GrailsWebMockUtil.bindMockWebRequest() + MockHttpServletRequest request = webRequest.currentRequest + linkParams.contextPath = '' + + when: "A namespace is specified" + linkParams.namespace = 'fooBar' + linkParams.controller = 'one' + linkParams.action = 'two' + + then: "it exists in the url" + link == '/fooBar/one/two' + + when: "The namespace is in the request params" + webRequest.setControllerNamespace("fooBarReq") + linkParams.remove('namespace') + linkParams.controller = 'one' + linkParams.action = 'two' + + then: "it exists in the url" + link == '/fooBarReq/one/two' + + when: "Params and the request attribute exist" + webRequest.setControllerNamespace("fooBarReq") + linkParams.namespace = 'fooBarParam' + linkParams.controller = 'one' + linkParams.action = 'two' + + then: "params wins" + link == '/fooBarParam/one/two' + } void cleanup() { RequestContextHolder.resetRequestAttributes() @@ -294,8 +326,8 @@ class LinkGeneratorSpec extends Specification { protected getGenerator(boolean cache=false) { def generator = cache ? new CachingLinkGenerator(baseUrl, context) : new DefaultLinkGenerator(baseUrl, context) final callable = { String controller, String action, String namespace, String pluginName, String httpMethod, Map params -> - [createRelativeURL: { String c, String a, Map parameterValues, String encoding, String fragment -> - "/$controller/$action".toString() + [createRelativeURL: { String c, String a, String n, String p, Map parameterValues, String encoding, String fragment -> + "${namespace ? '/' + namespace : ''}/$controller/$action".toString() }] as UrlCreator } generator.grailsUrlConverter = new CamelCaseUrlConverter() diff --git a/trigger-dependent-build.sh b/trigger-dependent-build.sh index 0dc697a9358..983b68ecdef 100755 --- a/trigger-dependent-build.sh +++ b/trigger-dependent-build.sh @@ -34,6 +34,11 @@ if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then exit 0 fi +# Only run for master branch +if [ "${TRAVIS_BRANCH}" != "master" ]; then + exit 0 +fi + # Don't run for tagged releases if [[ $TRAVIS_TAG =~ ^v[[:digit:]] ]]; then exit 0