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 14de639d420..40aa581c6be 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011 SpringSource + * Copyright 2011-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -189,7 +189,7 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware { } } List tokens = resource.contains('/') ? resource.tokenize('/') :[resource] - controller = tokens[-1] + controller = controllerAttribute?:tokens[-1] if (tokens.size()>1) { for(t in tokens[0..-2]) { final key = "${t}Id".toString() @@ -291,6 +291,20 @@ class DefaultLinkGenerator implements LinkGenerator, PluginManagerAware { @CompileStatic(TypeCheckingMode.SKIP) protected String getResourceId(resourceAttribute) { + try { + // Three options for using indent(): + // 1. Check instanceof GormEntity, but that would require coupling web-common to grails-datastore-gorm + // 2. GrailsMetaClassUtils.invokeMethodIfExists(o, "ident", new Object[0]); Slow? + // 3. Just assuming resource is a GormEntity or has ident() implemented and catching an exception if it is not. + def ident = resourceAttribute.ident() + if (ident) { + return ident.toString() + } + } catch (MissingMethodException | IllegalStateException ignored) { + // An IllegalStateException occurs if GORM is not initialized. + // A MissingMethodException if it is not a GormEntity + } + final id = resourceAttribute.id if (id) { return id.toString() diff --git a/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/LinkGeneratorSpec.groovy b/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/LinkGeneratorSpec.groovy index 77ce45e3d78..75e498c2d53 100644 --- a/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/LinkGeneratorSpec.groovy +++ b/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/LinkGeneratorSpec.groovy @@ -1,5 +1,6 @@ package org.grails.web.mapping +import grails.artefact.Artefact import grails.core.DefaultGrailsApplication import grails.plugins.DefaultGrailsPluginManager import grails.util.GrailsWebMockUtil @@ -289,6 +290,29 @@ class LinkGeneratorSpec extends Specification { cacheKey == "somePrefix[resource:org.grails.web.mapping.Widget->2]" } + @Issue('https://github.com/grails/grails-core/issues/13627') + def 'resource links should use ident and allow controller override'() { + given: + final webRequest = GrailsWebMockUtil.bindMockWebRequest() + MockHttpServletRequest request = webRequest.currentRequest + linkParams.method = 'GET' + + when: 'a resource is specified, ident() is used for id' + linkParams.resource = new Widget(id: 1, name: 'Some Widget') + + then: + link == "/bar/widget/show/1" + + then: + linkParams.resource.identCalled + + when: "A controller is specified" + linkParams.controller = 'widgetAdmin' + + then: + link == "/bar/widgetAdmin/show/1" + } + def 'link should take into affect namespace'() { given: final webRequest = GrailsWebMockUtil.bindMockWebRequest() @@ -341,7 +365,8 @@ class LinkGeneratorSpec extends Specification { 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, String n, String p, Map parameterValues, String encoding, String fragment -> - "${namespace ? '/' + namespace : ''}/$controller/$action".toString() + + "${namespace ? '/' + namespace : ''}/$controller/$action${parameterValues.id? '/'+parameterValues.id:''}".toString() }] as UrlCreator } generator.grailsUrlConverter = new CamelCaseUrlConverter() @@ -381,11 +406,14 @@ class LinkGeneratorSpec extends Specification { } } +@Artefact('Domain') class Widget { Long id String name + boolean identCalled = false Long ident() { + identCalled = true id }