You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/04/08 04:04:47 UTC

(grails-core) 01/01: GRAILS-10683 - improve rendering empty collections

This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch expandedTests/GRAILS-10683b
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit e633d4e7404b9c88a72777a6ad6806b36b7dec4d
Author: Jeff Scott Brown <jb...@gopivotal.com>
AuthorDate: Tue Dec 3 11:18:39 2013 -0600

    GRAILS-10683 - improve rendering empty collections
---
 .../plugins/web/rest/api/ControllersRestApi.groovy |  34 ++++++-
 .../web/rest/render/DefaultRendererRegistry.groovy |   2 +
 .../web/rest/transform/ResourceTransform.groovy    |   1 +
 .../test/mixin/web/ControllerUnitTestMixin.groovy  |   6 +-
 .../RespondMethodWithEmptyCollectionSpec.groovy    | 106 +++++++++++++++++++++
 5 files changed, 141 insertions(+), 8 deletions(-)

diff --git a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/api/ControllersRestApi.groovy b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/api/ControllersRestApi.groovy
index d505edabd5..fe9515f581 100644
--- a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/api/ControllersRestApi.groovy
+++ b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/api/ControllersRestApi.groovy
@@ -80,6 +80,25 @@ class ControllersRestApi {
      * @return
      */
     Object respond(Object controller, Object value, Map args = [:]) {
+        respond controller, null, value, args
+    }
+    
+    /**
+     * The respond method will attempt to delivery an appropriate response for the
+     * requested response format and value.
+     *
+     * If the value is null then a 404 will be returned. Otherwise the {@link RendererRegistry}
+     * will be consulted for an appropriate response renderer for the requested response format.
+     *
+     * @param controller The controller
+     * @param componentType The type of component being rendered. If null the type will be the 
+     * type of value unless value is a Collection in which case the type will be the type of 
+     * the first element in the collection.  This value is used to locate an appropriate Renderer.
+     * @param value The value
+     * @param args The arguments
+     * @return
+     */
+    Object respond(Object controller, Class componentType, Object value, Map args = [:]) {
         Integer statusCode
         if (args.status) {
             final Object statusValue = args.status
@@ -94,7 +113,7 @@ class ControllersRestApi {
         }
 
         final webRequest = getWebRequest(controller)
-        List<String> formats = calculateFormats(controller, webRequest.actionName, value, args)
+        List<String> formats = calculateFormats(controller, webRequest.actionName, componentType ?: value, args)
         final response = webRequest.getCurrentResponse()
         MimeType mimeType = getResponseFormat(response)
         def registry = rendererRegistry
@@ -134,12 +153,12 @@ class ControllersRestApi {
 
             final valueType = value.getClass()
             if (registry.isContainerType(valueType)) {
-                renderer = registry.findContainerRenderer(mimeType,valueType, value)
+                renderer = registry.findContainerRenderer(mimeType,valueType, componentType ?: value)
                 if (renderer == null) {
-                    renderer = registry.findRenderer(mimeType, value)
+                    renderer = registry.findRenderer(mimeType, componentType ?: value)
                 }
             } else {
-                renderer = registry.findRenderer(mimeType, value)
+                renderer = registry.findRenderer(mimeType, componentType ?: value)
             }
 
             if (renderer) {
@@ -181,7 +200,12 @@ class ControllersRestApi {
     }
 
     protected List<String> getDefaultResponseFormats(value) {
-        Resource resAnn = value != null ? value.getClass().getAnnotation(Resource) : null
+        Resource resAnn 
+        if(value instanceof Class) {
+            resAnn = value.getAnnotation(Resource)
+        } else {
+            resAnn = value != null ? value.getClass().getAnnotation(Resource) : null
+        }
         if (resAnn) {
             return resAnn.formats().toList()
         }
diff --git a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/render/DefaultRendererRegistry.groovy b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/render/DefaultRendererRegistry.groovy
index db1e2103a7..bf86ac9ab4 100644
--- a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/render/DefaultRendererRegistry.groovy
+++ b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/render/DefaultRendererRegistry.groovy
@@ -184,6 +184,8 @@ class DefaultRendererRegistry extends ClassAndMimeTypeRegistry<Renderer, Rendere
         Class targetClass = containerClass
         if (containerClass.isArray()) {
             targetClass = containerClass.getComponentType()
+        } else if(object instanceof Class) {
+            targetClass = object
         } else if (object instanceof Iterable) {
             if (object) {
                 final iterator = object.iterator()
diff --git a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/transform/ResourceTransform.groovy b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/transform/ResourceTransform.groovy
index c595e7986c..09f3c63fd4 100644
--- a/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/transform/ResourceTransform.groovy
+++ b/grails-plugin-rest/src/main/groovy/org/grails/plugins/web/rest/transform/ResourceTransform.groovy
@@ -505,6 +505,7 @@ class ResourceTransform implements ASTTransformation{
         def listCall = applyDefaultMethodTarget(new MethodCallExpression(domainExpr, new ConstantExpression("list"), listArgs), domainClass)
         def countCall = applyDefaultMethodTarget(new MethodCallExpression(domainExpr, new ConstantExpression("count"), MethodCallExpression.NO_ARGUMENTS), domainClass)
         def respondArgs = new ArgumentListExpression()
+        respondArgs.addExpression(domainExpr)
         respondArgs.addExpression(listCall)
         final args = new MapExpression()
         final model = new MapExpression()
diff --git a/grails-plugin-testing/src/main/groovy/grails/test/mixin/web/ControllerUnitTestMixin.groovy b/grails-plugin-testing/src/main/groovy/grails/test/mixin/web/ControllerUnitTestMixin.groovy
index 71f2cbd726..70d711b0c5 100644
--- a/grails-plugin-testing/src/main/groovy/grails/test/mixin/web/ControllerUnitTestMixin.groovy
+++ b/grails-plugin-testing/src/main/groovy/grails/test/mixin/web/ControllerUnitTestMixin.groovy
@@ -182,9 +182,9 @@ class ControllerUnitTestMixin extends GrailsUnitTestMixin {
         defineBeans(new ConvertersGrailsPlugin().doWithSpring)
         defineBeans {
             instanceControllersApi(ControllersApi)
-            final rendererRegistry = new DefaultRendererRegistry()
-            rendererRegistry.modelSuffix = config.flatten().get('grails.scaffolding.templates.domainSuffix') ?: ''
-            rendererRegistry.initialize()
+            rendererRegistry(DefaultRendererRegistry) {
+               modelSuffix = config.flatten().get('grails.scaffolding.templates.domainSuffix') ?: ''
+            }
             instanceControllersRestApi(ControllersRestApi, rendererRegistry, ref("instanceControllersApi"), new ControllersMimeTypesApi())
             instanceControllerTagLibraryApi(ControllerTagLibraryApi)
 
diff --git a/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodWithEmptyCollectionSpec.groovy b/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodWithEmptyCollectionSpec.groovy
new file mode 100644
index 0000000000..dc48b4dc62
--- /dev/null
+++ b/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodWithEmptyCollectionSpec.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package grails.rest.web
+
+import grails.artefact.Artefact
+import grails.persistence.Entity
+import grails.rest.Resource
+import grails.rest.render.json.JsonCollectionRenderer
+import grails.test.mixin.Mock
+import grails.test.mixin.TestFor
+
+import org.codehaus.groovy.grails.plugins.web.mimes.MimeTypesFactoryBean
+
+import spock.lang.Ignore
+import spock.lang.Issue
+import spock.lang.Specification
+
+@TestFor(GadgetController)
+@Mock(Gadget)
+class RespondMethodWithEmptyCollectionSpec extends Specification{
+
+    void setup() {
+        def ga = grailsApplication
+        ga.config.grails.mime.types =
+            [ html: ['text/html','application/xhtml+xml'],
+            xml: ['text/xml', 'application/xml'],
+            text: 'text/plain',
+            js: 'text/javascript',
+            rss: 'application/rss+xml',
+            atom: 'application/atom+xml',
+            css: 'text/css',
+            csv: 'text/csv',
+            all: '*/*',
+            json: ['application/json','text/json'],
+            form: 'application/x-www-form-urlencoded',
+            multipartForm: 'multipart/form-data'
+        ]
+            
+    }
+    
+    def setupSpec() {
+        defineBeans {
+            mimeTypes(MimeTypesFactoryBean) {
+                grailsApplication = ref('grailsApplication')
+            }
+            gadgetCollectionRenderer(JsonCollectionRenderer, Gadget, 'application/json') {
+                excludes=['class']
+            }
+        }
+    }
+
+    @Issue('GRAILS-10683')
+    void "Test responding to a REST call with an empty collection"() {
+
+        when:
+        response.format = 'json'
+        controller.index()
+
+        then:
+        response.status == 200
+        response.contentAsString == '[]'
+    }
+    
+    @Issue('GRAILS-10683')
+    @Ignore
+    void "Test responding to an HTML call with an empty collection"() {
+        when:
+        response.format = 'html'
+        controller.index()
+
+        then:
+        response.status == 200
+    }
+}
+
+@Artefact("Controller")
+class GadgetController {
+
+    def index() {
+        respond Gadget, [] 
+    }
+}
+@Entity
+@Resource(formats=['json', 'html'])
+class Gadget {
+    String title
+
+    static constraints = {
+        title blank:false
+    }
+}
+