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

danhaywood pushed a commit to branch CAUSEWAY-3676
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 07cf38b6a6fc06027babad0197ac48e2a61b23e5
Author: danhaywood <[email protected]>
AuthorDate: Sun Feb 18 07:34:39 2024 +0000

    CAUSEWAY-3676: downloads icon as URL
---
 .../viewer/graphql/model/domain/GqlvMeta.java      |  26 +--
 .../{GqlvMetaIconName.java => GqlvMetaIcon.java}   |   8 +-
 ...gTest.create_staff_member_with_department._.gql |   2 +-
 ...eate_staff_member_with_department.approved.json |   2 +-
 ...d_staff_member_by_name_and_download_photo._.gql |   1 +
 ...member_by_name_and_download_photo.approved.json |   3 +-
 .../e2e/queryandmutations/Staff_IntegTest.java     |  13 +-
 viewers/graphql/test/src/test/resources/schema.gql | 222 ++++++++++-----------
 .../viewer/controller/ResourceController.java      |  28 ++-
 9 files changed, 172 insertions(+), 133 deletions(-)

diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java
index cdec355cc8..7249f31e69 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java
@@ -18,7 +18,6 @@
  */
 package org.apache.causeway.viewer.graphql.model.domain;
 
-import java.util.Objects;
 import java.util.Optional;
 
 import graphql.schema.DataFetchingEnvironment;
@@ -26,10 +25,8 @@ import graphql.schema.DataFetchingEnvironment;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.applib.services.bookmark.BookmarkService;
 import org.apache.causeway.applib.services.metamodel.BeanSort;
-import org.apache.causeway.commons.io.JaxbUtils;
 import 
org.apache.causeway.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet;
-import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet;
 import org.apache.causeway.core.metamodel.facets.object.layout.LayoutFacet;
 import org.apache.causeway.core.metamodel.object.Bookmarkable;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -46,7 +43,7 @@ public class GqlvMeta extends GqlvAbstractCustom {
     private final GqlvMetaLogicalTypeName metaLogicalTypeName;
     private final GqlvMetaVersion metaVersion;
     private final GqlvMetaTitle metaTitle;
-    private final GqlvMetaIconName metaIconName;
+    private final GqlvMetaIcon metaIconName;
     private final GqlvMetaCssClass metaCssClass;
     private final GqlvMetaLayout metaLayout;
     private final GqlvMetaGrid metaGrid;
@@ -76,7 +73,7 @@ public class GqlvMeta extends GqlvAbstractCustom {
         addChildFieldFor(this.metaLogicalTypeName = new 
GqlvMetaLogicalTypeName(context));
         addChildFieldFor(this.metaVersion = isEntity() ? new 
GqlvMetaVersion(context) : null);
         addChildFieldFor(this.metaTitle = new GqlvMetaTitle(context));
-        addChildFieldFor(this.metaIconName = new GqlvMetaIconName(context));
+        addChildFieldFor(this.metaIconName = new GqlvMetaIcon(context));
         addChildFieldFor(this.metaCssClass = new GqlvMetaCssClass(context));
         addChildFieldFor(this.metaLayout = new GqlvMetaLayout(context));
         addChildFieldFor(this.metaGrid = new GqlvMetaGrid(context));
@@ -166,12 +163,6 @@ public class GqlvMeta extends GqlvAbstractCustom {
                     .orElse(null);
         }
 
-        public String iconName() {
-            return managedObject()
-                    .map(ManagedObject::getIconName)
-                    .orElse(null);
-        }
-
         public String cssClass() {
             return managedObject()
                     .map(managedObject -> {
@@ -191,10 +182,19 @@ public class GqlvMeta extends GqlvAbstractCustom {
         }
 
         public String grid() {
+            return resource("grid");
+        }
+
+        public String icon() {
+            return resource("icon");
+        }
+
+        private String resource(String resource) {
             return managedObject()
                     .flatMap(Bookmarkable::getBookmark
-                    ).map(x -> String.format(
-                    "///%s/object/%s:%s/grid", "graphql", 
x.getLogicalTypeName(), x.getIdentifier()))
+                    ).map(bookmark -> String.format(
+                            "///%s/object/%s:%s/_meta/%s",
+                            "graphql", bookmark.getLogicalTypeName(), 
bookmark.getIdentifier(), resource) )
                     .orElse(null);
         }
 
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIconName.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIcon.java
similarity index 82%
rename from 
viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIconName.java
rename to 
viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIcon.java
index d6b841fde6..f9478dbe92 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIconName.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaIcon.java
@@ -25,17 +25,17 @@ import static 
graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 
 import org.apache.causeway.viewer.graphql.model.context.Context;
 
-public class GqlvMetaIconName extends GqlvAbstract {
+public class GqlvMetaIcon extends GqlvAbstract {
 
-    public GqlvMetaIconName(final Context context) {
+    public GqlvMetaIcon(final Context context) {
         super(context);
 
-        
setField(newFieldDefinition().name("iconName").type(Scalars.GraphQLString).build());
+        
setField(newFieldDefinition().name("icon").type(Scalars.GraphQLString).build());
     }
 
     @Override
     protected String fetchData(DataFetchingEnvironment environment) {
-        return environment.<GqlvMeta.Fetcher>getSource().iconName();
+        return environment.<GqlvMeta.Fetcher>getSource().icon();
     }
 
 }
diff --git 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql
 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql
index b18a3e5a64..b9a4120209 100644
--- 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql
+++ 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql
@@ -31,7 +31,7 @@
               logicalTypeName
               version
               cssClass
-              iconName
+              icon
             }
           }
         }
diff --git 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json
 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json
index 4548f25fb4..88838735ff 100644
--- 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json
+++ 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json
@@ -32,7 +32,7 @@
                 "logicalTypeName" : "university.dept.StaffMember",
                 "version" : null,
                 "cssClass" : null,
-                "iconName" : null
+                "icon" : 
"///graphql/object/university.dept.StaffMember:NNN/icon"
               }
             }
           }
diff --git 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo._.gql
 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo._.gql
index 7bcd1390a7..f678e21bb2 100644
--- 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo._.gql
+++ 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo._.gql
@@ -7,6 +7,7 @@
         }
         _meta {
           grid
+          icon
         }
         photo {
           get {
diff --git 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo.approved.json
 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo.approved.json
index 4f69d1a11a..1d2d11f8f4 100644
--- 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo.approved.json
+++ 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.find_staff_member_by_name_and_download_photo.approved.json
@@ -7,7 +7,8 @@
             "get" : "Gerry Jones"
           },
           "_meta" : {
-            "grid" : "///graphql/object/university.dept.StaffMember:NNN/grid"
+            "grid" : "///graphql/object/university.dept.StaffMember:NNN/grid",
+            "icon" : "///graphql/object/university.dept.StaffMember:NNN/icon"
           },
           "photo" : {
             "get" : {
diff --git 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.java
 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.java
index a67b433248..f992595a07 100644
--- 
a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.java
+++ 
b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.java
@@ -137,7 +137,7 @@ public class Staff_IntegTest extends Abstract_IntegTest {
                 
.at("/data/university_dept_Staff/findStaffMemberByName/invoke/_meta/grid")
                 .asText();
 
-        
assertThat(gridUrl).matches("///graphql/object/university.dept.StaffMember:(\\d+)/grid");
+        
assertThat(gridUrl).matches("///graphql/object/university.dept.StaffMember:(\\d+)/_meta/grid");
         val gridHttpResponse = submitReturningString(gridUrl);
 
         assertThat(gridHttpResponse.statusCode()).isEqualTo(200);
@@ -154,6 +154,17 @@ public class Staff_IntegTest extends Abstract_IntegTest {
         assertThat(photoBytesResponse.statusCode()).isEqualTo(200);
         val photoBytes = photoBytesResponse.body();
         assertThat(photoBytes).isNotEmpty();
+
+
+        val iconBytesUrl = jsonNodeRoot
+                
.at("/data/university_dept_Staff/findStaffMemberByName/invoke/_meta/icon")
+                .asText();
+
+        
assertThat(iconBytesUrl).matches("///graphql/object/university.dept.StaffMember:(\\d+)/_meta/icon");
+        val iconBytesResponse = submitReturningBytes(iconBytesUrl);
+        assertThat(iconBytesResponse.statusCode()).isEqualTo(200);
+        val iconBytes = iconBytesResponse.body();
+        assertThat(iconBytes).isNotEmpty();
     }
 
     private HttpResponse<byte[]> submitReturningBytes(String url) throws 
IOException, InterruptedException {
diff --git a/viewers/graphql/test/src/test/resources/schema.gql 
b/viewers/graphql/test/src/test/resources/schema.gql
index 8c377660d5..ad5e32ac20 100644
--- a/viewers/graphql/test/src/test/resources/schema.gql
+++ b/viewers/graphql/test/src/test/resources/schema.gql
@@ -68,51 +68,51 @@ type Mutation {
 
 type Query {
   Scenario(name: String): Scenario
-  causeway_applib_DomainObjectList(object: 
causeway_applib_DomainObjectList__gqlv_input): 
[causeway_applib_DomainObjectList]
-  causeway_applib_FacetGroupNode(object: 
causeway_applib_FacetGroupNode__gqlv_input): [causeway_applib_FacetGroupNode]
-  causeway_applib_ParameterNode(object: 
causeway_applib_ParameterNode__gqlv_input): [causeway_applib_ParameterNode]
-  causeway_applib_PropertyNode(object: 
causeway_applib_PropertyNode__gqlv_input): [causeway_applib_PropertyNode]
-  causeway_applib_RoleMemento(object: 
causeway_applib_RoleMemento__gqlv_input): [causeway_applib_RoleMemento]
-  causeway_applib_TypeNode(object: causeway_applib_TypeNode__gqlv_input): 
[causeway_applib_TypeNode]
-  causeway_applib_UserMemento(object: 
causeway_applib_UserMemento__gqlv_input): [causeway_applib_UserMemento]
+  causeway_applib_DomainObjectList(object: 
causeway_applib_DomainObjectList__gqlv_input): causeway_applib_DomainObjectList
+  causeway_applib_FacetGroupNode(object: 
causeway_applib_FacetGroupNode__gqlv_input): causeway_applib_FacetGroupNode
+  causeway_applib_ParameterNode(object: 
causeway_applib_ParameterNode__gqlv_input): causeway_applib_ParameterNode
+  causeway_applib_PropertyNode(object: 
causeway_applib_PropertyNode__gqlv_input): causeway_applib_PropertyNode
+  causeway_applib_RoleMemento(object: 
causeway_applib_RoleMemento__gqlv_input): causeway_applib_RoleMemento
+  causeway_applib_TypeNode(object: causeway_applib_TypeNode__gqlv_input): 
causeway_applib_TypeNode
+  causeway_applib_UserMemento(object: 
causeway_applib_UserMemento__gqlv_input): causeway_applib_UserMemento
   causeway_applib_UserMenu: causeway_applib_UserMenu
-  causeway_applib_node_ActionNode(object: 
causeway_applib_node_ActionNode__gqlv_input): [causeway_applib_node_ActionNode]
-  causeway_applib_node_CollectionNode(object: 
causeway_applib_node_CollectionNode__gqlv_input): 
[causeway_applib_node_CollectionNode]
-  causeway_applib_node_FacetAttrNode(object: 
causeway_applib_node_FacetAttrNode__gqlv_input): 
[causeway_applib_node_FacetAttrNode]
-  causeway_applib_node_FacetNode(object: 
causeway_applib_node_FacetNode__gqlv_input): [causeway_applib_node_FacetNode]
+  causeway_applib_node_ActionNode(object: 
causeway_applib_node_ActionNode__gqlv_input): causeway_applib_node_ActionNode
+  causeway_applib_node_CollectionNode(object: 
causeway_applib_node_CollectionNode__gqlv_input): 
causeway_applib_node_CollectionNode
+  causeway_applib_node_FacetAttrNode(object: 
causeway_applib_node_FacetAttrNode__gqlv_input): 
causeway_applib_node_FacetAttrNode
+  causeway_applib_node_FacetNode(object: 
causeway_applib_node_FacetNode__gqlv_input): causeway_applib_node_FacetNode
   causeway_conf_ConfigurationMenu: causeway_conf_ConfigurationMenu
-  causeway_conf_ConfigurationProperty(object: 
causeway_conf_ConfigurationProperty__gqlv_input): 
[causeway_conf_ConfigurationProperty]
-  causeway_conf_ConfigurationViewmodel(object: 
causeway_conf_ConfigurationViewmodel__gqlv_input): 
[causeway_conf_ConfigurationViewmodel]
-  causeway_feat_ApplicationFeatureViewModel(object: 
causeway_feat_ApplicationFeatureViewModel__gqlv_input): 
[causeway_feat_ApplicationFeatureViewModel]
-  causeway_feat_ApplicationNamespace(object: 
causeway_feat_ApplicationNamespace__gqlv_input): 
[causeway_feat_ApplicationNamespace]
-  causeway_feat_ApplicationType(object: 
causeway_feat_ApplicationType__gqlv_input): [causeway_feat_ApplicationType]
-  causeway_feat_ApplicationTypeAction(object: 
causeway_feat_ApplicationTypeAction__gqlv_input): 
[causeway_feat_ApplicationTypeAction]
-  causeway_feat_ApplicationTypeCollection(object: 
causeway_feat_ApplicationTypeCollection__gqlv_input): 
[causeway_feat_ApplicationTypeCollection]
-  causeway_feat_ApplicationTypeMember(object: 
causeway_feat_ApplicationTypeMember__gqlv_input): 
[causeway_feat_ApplicationTypeMember]
-  causeway_feat_ApplicationTypeProperty(object: 
causeway_feat_ApplicationTypeProperty__gqlv_input): 
[causeway_feat_ApplicationTypeProperty]
-  causeway_schema_metamodel_v2_DomainClassDto(object: 
causeway_schema_metamodel_v2_DomainClassDto__gqlv_input): 
[causeway_schema_metamodel_v2_DomainClassDto]
-  causeway_security_LoginRedirect(object: 
causeway_security_LoginRedirect__gqlv_input): [causeway_security_LoginRedirect]
+  causeway_conf_ConfigurationProperty(object: 
causeway_conf_ConfigurationProperty__gqlv_input): 
causeway_conf_ConfigurationProperty
+  causeway_conf_ConfigurationViewmodel(object: 
causeway_conf_ConfigurationViewmodel__gqlv_input): 
causeway_conf_ConfigurationViewmodel
+  causeway_feat_ApplicationFeatureViewModel(object: 
causeway_feat_ApplicationFeatureViewModel__gqlv_input): 
causeway_feat_ApplicationFeatureViewModel
+  causeway_feat_ApplicationNamespace(object: 
causeway_feat_ApplicationNamespace__gqlv_input): 
causeway_feat_ApplicationNamespace
+  causeway_feat_ApplicationType(object: 
causeway_feat_ApplicationType__gqlv_input): causeway_feat_ApplicationType
+  causeway_feat_ApplicationTypeAction(object: 
causeway_feat_ApplicationTypeAction__gqlv_input): 
causeway_feat_ApplicationTypeAction
+  causeway_feat_ApplicationTypeCollection(object: 
causeway_feat_ApplicationTypeCollection__gqlv_input): 
causeway_feat_ApplicationTypeCollection
+  causeway_feat_ApplicationTypeMember(object: 
causeway_feat_ApplicationTypeMember__gqlv_input): 
causeway_feat_ApplicationTypeMember
+  causeway_feat_ApplicationTypeProperty(object: 
causeway_feat_ApplicationTypeProperty__gqlv_input): 
causeway_feat_ApplicationTypeProperty
+  causeway_schema_metamodel_v2_DomainClassDto(object: 
causeway_schema_metamodel_v2_DomainClassDto__gqlv_input): 
causeway_schema_metamodel_v2_DomainClassDto
+  causeway_security_LoginRedirect(object: 
causeway_security_LoginRedirect__gqlv_input): causeway_security_LoginRedirect
   causeway_security_LogoutMenu: causeway_security_LogoutMenu
-  causeway_testing_fixtures_FixtureResult(object: 
causeway_testing_fixtures_FixtureResult__gqlv_input): 
[causeway_testing_fixtures_FixtureResult]
-  java_lang_Runnable(object: java_lang_Runnable__gqlv_input): 
[java_lang_Runnable]
-  java_util_Map(object: java_util_Map__gqlv_input): [java_util_Map]
-  java_util_SortedMap(object: java_util_SortedMap__gqlv_input): 
[java_util_SortedMap]
-  java_util_concurrent_Callable(object: 
java_util_concurrent_Callable__gqlv_input): [java_util_concurrent_Callable]
-  java_util_function_BiFunction(object: 
java_util_function_BiFunction__gqlv_input): [java_util_function_BiFunction]
-  java_util_function_Consumer(object: 
java_util_function_Consumer__gqlv_input): [java_util_function_Consumer]
-  java_util_function_Function(object: 
java_util_function_Function__gqlv_input): [java_util_function_Function]
-  java_util_stream_Stream(object: java_util_stream_Stream__gqlv_input): 
[java_util_stream_Stream]
-  org_apache_causeway_core_metamodel_inspect_model_MMNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_input): 
[org_apache_causeway_core_metamodel_inspect_model_MMNode]
-  org_apache_causeway_core_metamodel_inspect_model_MemberNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_input): 
[org_apache_causeway_core_metamodel_inspect_model_MemberNode]
-  
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript(object:
 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input):
 [org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript]
+  causeway_testing_fixtures_FixtureResult(object: 
causeway_testing_fixtures_FixtureResult__gqlv_input): 
causeway_testing_fixtures_FixtureResult
+  java_lang_Runnable(object: java_lang_Runnable__gqlv_input): 
java_lang_Runnable
+  java_util_Map(object: java_util_Map__gqlv_input): java_util_Map
+  java_util_SortedMap(object: java_util_SortedMap__gqlv_input): 
java_util_SortedMap
+  java_util_concurrent_Callable(object: 
java_util_concurrent_Callable__gqlv_input): java_util_concurrent_Callable
+  java_util_function_BiFunction(object: 
java_util_function_BiFunction__gqlv_input): java_util_function_BiFunction
+  java_util_function_Consumer(object: 
java_util_function_Consumer__gqlv_input): java_util_function_Consumer
+  java_util_function_Function(object: 
java_util_function_Function__gqlv_input): java_util_function_Function
+  java_util_stream_Stream(object: java_util_stream_Stream__gqlv_input): 
java_util_stream_Stream
+  org_apache_causeway_core_metamodel_inspect_model_MMNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_input): 
org_apache_causeway_core_metamodel_inspect_model_MMNode
+  org_apache_causeway_core_metamodel_inspect_model_MemberNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_input): 
org_apache_causeway_core_metamodel_inspect_model_MemberNode
+  
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript(object:
 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input):
 org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript
   university_admin_AdminMenu: university_admin_AdminMenu
   university_calc_Calculator: university_calc_Calculator
-  university_dept_Department(object: university_dept_Department__gqlv_input): 
[university_dept_Department]
+  university_dept_Department(object: university_dept_Department__gqlv_input): 
university_dept_Department
   university_dept_Departments: university_dept_Departments
-  university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): 
[university_dept_DeptHead]
+  university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): 
university_dept_DeptHead
   university_dept_DeptHeads: university_dept_DeptHeads
   university_dept_Staff: university_dept_Staff
-  university_dept_StaffMember(object: 
university_dept_StaffMember__gqlv_input): [university_dept_StaffMember]
+  university_dept_StaffMember(object: 
university_dept_StaffMember__gqlv_input): university_dept_StaffMember
 }
 
 type Scenario {
@@ -123,51 +123,51 @@ type Scenario {
 }
 
 type ScenarioStep {
-  causeway_applib_DomainObjectList(object: 
causeway_applib_DomainObjectList__gqlv_input): 
[causeway_applib_DomainObjectList]
-  causeway_applib_FacetGroupNode(object: 
causeway_applib_FacetGroupNode__gqlv_input): [causeway_applib_FacetGroupNode]
-  causeway_applib_ParameterNode(object: 
causeway_applib_ParameterNode__gqlv_input): [causeway_applib_ParameterNode]
-  causeway_applib_PropertyNode(object: 
causeway_applib_PropertyNode__gqlv_input): [causeway_applib_PropertyNode]
-  causeway_applib_RoleMemento(object: 
causeway_applib_RoleMemento__gqlv_input): [causeway_applib_RoleMemento]
-  causeway_applib_TypeNode(object: causeway_applib_TypeNode__gqlv_input): 
[causeway_applib_TypeNode]
-  causeway_applib_UserMemento(object: 
causeway_applib_UserMemento__gqlv_input): [causeway_applib_UserMemento]
+  causeway_applib_DomainObjectList(object: 
causeway_applib_DomainObjectList__gqlv_input): causeway_applib_DomainObjectList
+  causeway_applib_FacetGroupNode(object: 
causeway_applib_FacetGroupNode__gqlv_input): causeway_applib_FacetGroupNode
+  causeway_applib_ParameterNode(object: 
causeway_applib_ParameterNode__gqlv_input): causeway_applib_ParameterNode
+  causeway_applib_PropertyNode(object: 
causeway_applib_PropertyNode__gqlv_input): causeway_applib_PropertyNode
+  causeway_applib_RoleMemento(object: 
causeway_applib_RoleMemento__gqlv_input): causeway_applib_RoleMemento
+  causeway_applib_TypeNode(object: causeway_applib_TypeNode__gqlv_input): 
causeway_applib_TypeNode
+  causeway_applib_UserMemento(object: 
causeway_applib_UserMemento__gqlv_input): causeway_applib_UserMemento
   causeway_applib_UserMenu: causeway_applib_UserMenu
-  causeway_applib_node_ActionNode(object: 
causeway_applib_node_ActionNode__gqlv_input): [causeway_applib_node_ActionNode]
-  causeway_applib_node_CollectionNode(object: 
causeway_applib_node_CollectionNode__gqlv_input): 
[causeway_applib_node_CollectionNode]
-  causeway_applib_node_FacetAttrNode(object: 
causeway_applib_node_FacetAttrNode__gqlv_input): 
[causeway_applib_node_FacetAttrNode]
-  causeway_applib_node_FacetNode(object: 
causeway_applib_node_FacetNode__gqlv_input): [causeway_applib_node_FacetNode]
+  causeway_applib_node_ActionNode(object: 
causeway_applib_node_ActionNode__gqlv_input): causeway_applib_node_ActionNode
+  causeway_applib_node_CollectionNode(object: 
causeway_applib_node_CollectionNode__gqlv_input): 
causeway_applib_node_CollectionNode
+  causeway_applib_node_FacetAttrNode(object: 
causeway_applib_node_FacetAttrNode__gqlv_input): 
causeway_applib_node_FacetAttrNode
+  causeway_applib_node_FacetNode(object: 
causeway_applib_node_FacetNode__gqlv_input): causeway_applib_node_FacetNode
   causeway_conf_ConfigurationMenu: causeway_conf_ConfigurationMenu
-  causeway_conf_ConfigurationProperty(object: 
causeway_conf_ConfigurationProperty__gqlv_input): 
[causeway_conf_ConfigurationProperty]
-  causeway_conf_ConfigurationViewmodel(object: 
causeway_conf_ConfigurationViewmodel__gqlv_input): 
[causeway_conf_ConfigurationViewmodel]
-  causeway_feat_ApplicationFeatureViewModel(object: 
causeway_feat_ApplicationFeatureViewModel__gqlv_input): 
[causeway_feat_ApplicationFeatureViewModel]
-  causeway_feat_ApplicationNamespace(object: 
causeway_feat_ApplicationNamespace__gqlv_input): 
[causeway_feat_ApplicationNamespace]
-  causeway_feat_ApplicationType(object: 
causeway_feat_ApplicationType__gqlv_input): [causeway_feat_ApplicationType]
-  causeway_feat_ApplicationTypeAction(object: 
causeway_feat_ApplicationTypeAction__gqlv_input): 
[causeway_feat_ApplicationTypeAction]
-  causeway_feat_ApplicationTypeCollection(object: 
causeway_feat_ApplicationTypeCollection__gqlv_input): 
[causeway_feat_ApplicationTypeCollection]
-  causeway_feat_ApplicationTypeMember(object: 
causeway_feat_ApplicationTypeMember__gqlv_input): 
[causeway_feat_ApplicationTypeMember]
-  causeway_feat_ApplicationTypeProperty(object: 
causeway_feat_ApplicationTypeProperty__gqlv_input): 
[causeway_feat_ApplicationTypeProperty]
-  causeway_schema_metamodel_v2_DomainClassDto(object: 
causeway_schema_metamodel_v2_DomainClassDto__gqlv_input): 
[causeway_schema_metamodel_v2_DomainClassDto]
-  causeway_security_LoginRedirect(object: 
causeway_security_LoginRedirect__gqlv_input): [causeway_security_LoginRedirect]
+  causeway_conf_ConfigurationProperty(object: 
causeway_conf_ConfigurationProperty__gqlv_input): 
causeway_conf_ConfigurationProperty
+  causeway_conf_ConfigurationViewmodel(object: 
causeway_conf_ConfigurationViewmodel__gqlv_input): 
causeway_conf_ConfigurationViewmodel
+  causeway_feat_ApplicationFeatureViewModel(object: 
causeway_feat_ApplicationFeatureViewModel__gqlv_input): 
causeway_feat_ApplicationFeatureViewModel
+  causeway_feat_ApplicationNamespace(object: 
causeway_feat_ApplicationNamespace__gqlv_input): 
causeway_feat_ApplicationNamespace
+  causeway_feat_ApplicationType(object: 
causeway_feat_ApplicationType__gqlv_input): causeway_feat_ApplicationType
+  causeway_feat_ApplicationTypeAction(object: 
causeway_feat_ApplicationTypeAction__gqlv_input): 
causeway_feat_ApplicationTypeAction
+  causeway_feat_ApplicationTypeCollection(object: 
causeway_feat_ApplicationTypeCollection__gqlv_input): 
causeway_feat_ApplicationTypeCollection
+  causeway_feat_ApplicationTypeMember(object: 
causeway_feat_ApplicationTypeMember__gqlv_input): 
causeway_feat_ApplicationTypeMember
+  causeway_feat_ApplicationTypeProperty(object: 
causeway_feat_ApplicationTypeProperty__gqlv_input): 
causeway_feat_ApplicationTypeProperty
+  causeway_schema_metamodel_v2_DomainClassDto(object: 
causeway_schema_metamodel_v2_DomainClassDto__gqlv_input): 
causeway_schema_metamodel_v2_DomainClassDto
+  causeway_security_LoginRedirect(object: 
causeway_security_LoginRedirect__gqlv_input): causeway_security_LoginRedirect
   causeway_security_LogoutMenu: causeway_security_LogoutMenu
-  causeway_testing_fixtures_FixtureResult(object: 
causeway_testing_fixtures_FixtureResult__gqlv_input): 
[causeway_testing_fixtures_FixtureResult]
-  java_lang_Runnable(object: java_lang_Runnable__gqlv_input): 
[java_lang_Runnable]
-  java_util_Map(object: java_util_Map__gqlv_input): [java_util_Map]
-  java_util_SortedMap(object: java_util_SortedMap__gqlv_input): 
[java_util_SortedMap]
-  java_util_concurrent_Callable(object: 
java_util_concurrent_Callable__gqlv_input): [java_util_concurrent_Callable]
-  java_util_function_BiFunction(object: 
java_util_function_BiFunction__gqlv_input): [java_util_function_BiFunction]
-  java_util_function_Consumer(object: 
java_util_function_Consumer__gqlv_input): [java_util_function_Consumer]
-  java_util_function_Function(object: 
java_util_function_Function__gqlv_input): [java_util_function_Function]
-  java_util_stream_Stream(object: java_util_stream_Stream__gqlv_input): 
[java_util_stream_Stream]
-  org_apache_causeway_core_metamodel_inspect_model_MMNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_input): 
[org_apache_causeway_core_metamodel_inspect_model_MMNode]
-  org_apache_causeway_core_metamodel_inspect_model_MemberNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_input): 
[org_apache_causeway_core_metamodel_inspect_model_MemberNode]
-  
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript(object:
 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input):
 [org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript]
+  causeway_testing_fixtures_FixtureResult(object: 
causeway_testing_fixtures_FixtureResult__gqlv_input): 
causeway_testing_fixtures_FixtureResult
+  java_lang_Runnable(object: java_lang_Runnable__gqlv_input): 
java_lang_Runnable
+  java_util_Map(object: java_util_Map__gqlv_input): java_util_Map
+  java_util_SortedMap(object: java_util_SortedMap__gqlv_input): 
java_util_SortedMap
+  java_util_concurrent_Callable(object: 
java_util_concurrent_Callable__gqlv_input): java_util_concurrent_Callable
+  java_util_function_BiFunction(object: 
java_util_function_BiFunction__gqlv_input): java_util_function_BiFunction
+  java_util_function_Consumer(object: 
java_util_function_Consumer__gqlv_input): java_util_function_Consumer
+  java_util_function_Function(object: 
java_util_function_Function__gqlv_input): java_util_function_Function
+  java_util_stream_Stream(object: java_util_stream_Stream__gqlv_input): 
java_util_stream_Stream
+  org_apache_causeway_core_metamodel_inspect_model_MMNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_input): 
org_apache_causeway_core_metamodel_inspect_model_MMNode
+  org_apache_causeway_core_metamodel_inspect_model_MemberNode(object: 
org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_input): 
org_apache_causeway_core_metamodel_inspect_model_MemberNode
+  
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript(object:
 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input):
 org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript
   university_admin_AdminMenu: university_admin_AdminMenu
   university_calc_Calculator: university_calc_Calculator
-  university_dept_Department(object: university_dept_Department__gqlv_input): 
[university_dept_Department]
+  university_dept_Department(object: university_dept_Department__gqlv_input): 
university_dept_Department
   university_dept_Departments: university_dept_Departments
-  university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): 
[university_dept_DeptHead]
+  university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): 
university_dept_DeptHead
   university_dept_DeptHeads: university_dept_DeptHeads
   university_dept_Staff: university_dept_Staff
-  university_dept_StaffMember(object: 
university_dept_StaffMember__gqlv_input): [university_dept_StaffMember]
+  university_dept_StaffMember(object: 
university_dept_StaffMember__gqlv_input): university_dept_StaffMember
 }
 
 type causeway_applib_DomainObjectList {
@@ -219,7 +219,7 @@ type 
causeway_applib_DomainObjectList__elementTypeFqcn__gqlv_property {
 type causeway_applib_DomainObjectList__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -267,7 +267,7 @@ type causeway_applib_FacetGroupNode__facets__gqlv_property {
 type causeway_applib_FacetGroupNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -299,7 +299,7 @@ type 
causeway_applib_ParameterNode__childNodes__gqlv_collection {
 type causeway_applib_ParameterNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -342,7 +342,7 @@ type 
causeway_applib_PropertyNode__childNodes__gqlv_collection {
 type causeway_applib_PropertyNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -395,7 +395,7 @@ type 
causeway_applib_RoleMemento__description__gqlv_property {
 type causeway_applib_RoleMemento__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -437,7 +437,7 @@ type 
causeway_applib_TypeNode__domainClassDto__gqlv_property {
 type causeway_applib_TypeNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -499,7 +499,7 @@ type causeway_applib_UserMemento__avatarUrl__gqlv_property {
 type causeway_applib_UserMemento__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -615,7 +615,7 @@ type 
causeway_applib_node_ActionNode__childNodes__gqlv_collection {
 type causeway_applib_node_ActionNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -667,7 +667,7 @@ type 
causeway_applib_node_CollectionNode__collection__gqlv_property {
 type causeway_applib_node_CollectionNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -718,7 +718,7 @@ type 
causeway_applib_node_FacetAttrNode__facetAttr__gqlv_property {
 type causeway_applib_node_FacetAttrNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -761,7 +761,7 @@ type causeway_applib_node_FacetNode__facet__gqlv_property {
 type causeway_applib_node_FacetNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -806,7 +806,7 @@ type causeway_conf_ConfigurationProperty {
 type causeway_conf_ConfigurationProperty__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -849,7 +849,7 @@ type 
causeway_conf_ConfigurationViewmodel__environment__gqlv_collection {
 type causeway_conf_ConfigurationViewmodel__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -882,7 +882,7 @@ type causeway_feat_ApplicationFeatureViewModel {
 type causeway_feat_ApplicationFeatureViewModel__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -943,7 +943,7 @@ type 
causeway_feat_ApplicationNamespace__contents__gqlv_collection {
 type causeway_feat_ApplicationNamespace__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1020,7 +1020,7 @@ type 
causeway_feat_ApplicationTypeAction__actionSemantics__gqlv_property {
 type causeway_feat_ApplicationTypeAction__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1103,7 +1103,7 @@ type 
causeway_feat_ApplicationTypeCollection__elementType__gqlv_property {
 type causeway_feat_ApplicationTypeCollection__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1157,7 +1157,7 @@ type causeway_feat_ApplicationTypeMember {
 type causeway_feat_ApplicationTypeMember__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1224,7 +1224,7 @@ type 
causeway_feat_ApplicationTypeProperty__derived__gqlv_property {
 type causeway_feat_ApplicationTypeProperty__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1311,7 +1311,7 @@ type 
causeway_feat_ApplicationType__collections__gqlv_collection {
 type causeway_feat_ApplicationType__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1413,7 +1413,7 @@ type 
causeway_schema_metamodel_v2_DomainClassDto__facets__gqlv_property {
 type causeway_schema_metamodel_v2_DomainClassDto__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1473,7 +1473,7 @@ type causeway_security_LoginRedirect {
 type causeway_security_LoginRedirect__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1521,7 +1521,7 @@ type 
causeway_testing_fixtures_FixtureResult__fixtureScriptClassName__gqlv_prope
 type causeway_testing_fixtures_FixtureResult__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1553,7 +1553,7 @@ type java_lang_Runnable {
 type java_lang_Runnable__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1568,7 +1568,7 @@ type java_util_Map {
 type java_util_Map__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1583,7 +1583,7 @@ type java_util_SortedMap {
 type java_util_SortedMap__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1598,7 +1598,7 @@ type java_util_concurrent_Callable {
 type java_util_concurrent_Callable__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1613,7 +1613,7 @@ type java_util_function_BiFunction {
 type java_util_function_BiFunction__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1628,7 +1628,7 @@ type java_util_function_Consumer {
 type java_util_function_Consumer__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1643,7 +1643,7 @@ type java_util_function_Function {
 type java_util_function_Function__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1658,7 +1658,7 @@ type java_util_stream_Stream {
 type java_util_stream_Stream__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1681,7 +1681,7 @@ type 
org_apache_causeway_core_metamodel_inspect_model_MMNode__childNodes__gqlv_c
 type org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1713,7 +1713,7 @@ type 
org_apache_causeway_core_metamodel_inspect_model_MemberNode__childNodes__gq
 type org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -1757,7 +1757,7 @@ type 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__f
 type 
org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_meta
 {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -2489,7 +2489,7 @@ type university_dept_Department__deptHead__gqlv_property {
 type university_dept_Department__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -2664,7 +2664,7 @@ type university_dept_DeptHead__department__gqlv_property {
 type university_dept_DeptHead__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
@@ -2744,7 +2744,7 @@ type 
university_dept_StaffMember__department__gqlv_property {
 type university_dept_StaffMember__gqlv_meta {
   cssClass: String
   grid: String
-  iconName: String
+  icon: String
   id: String!
   layout: String
   logicalTypeName: String!
diff --git 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java
 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java
index 7f82ab8cf5..8e792e94b2 100644
--- 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java
+++ 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java
@@ -9,6 +9,8 @@ import org.apache.causeway.applib.layout.grid.Grid;
 import org.apache.causeway.commons.io.JaxbUtils;
 import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet;
 
+import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon;
+
 import org.springframework.http.ContentDisposition;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -74,7 +76,7 @@ public class ResourceController {
                 .orElse(ResponseEntity.notFound().build());
     }
 
-    @GetMapping(value = "/{logicalTypeName}:{id}/grid")
+    @GetMapping(value = "/{logicalTypeName}:{id}/_meta/grid")
     public ResponseEntity<String> grid(
             @PathVariable final String logicalTypeName,
             @PathVariable final String id
@@ -92,12 +94,36 @@ public class ResourceController {
                 .orElse(ResponseEntity.notFound().build());
     }
 
+    @GetMapping(value = "/{logicalTypeName}:{id}/_meta/icon")
+    public ResponseEntity<byte[]> icon(
+            @PathVariable final String logicalTypeName,
+            @PathVariable final String id
+    ) {
+        return lookup(logicalTypeName, id)
+                .map(ManagedObject::getIcon)
+                .filter(Objects::nonNull)
+                .map(objectIcon -> {
+                    val bytes = objectIcon.asBytes();
+                    return ResponseEntity.ok()
+                            
.contentType(MediaType.parseMediaType(objectIcon.getMimeType().getMimeType().toString()))
+                            .header(HttpHeaders.CONTENT_DISPOSITION, 
ContentDisposition.attachment().filename(logicalTypeName + 
".png").build().toString())
+                            .contentLength(bytes.length)
+                            .body(bytes);
+                })
+                .orElse(ResponseEntity.notFound().build());
+    }
+
     @Nullable
     private static Grid gridOf(ManagedObject managedObject) {
         val facet = managedObject.getSpecification().getFacet(GridFacet.class);
         return facet != null ? facet.getGrid(managedObject) : null;
     }
 
+    @Nullable
+    private static ObjectIcon iconOf(ManagedObject managedObject) {
+        return managedObject.getIcon();
+    }
+
     private Optional<Object> valueOfProperty(String logicalTypeName, String 
id, String propertyId) {
         return lookup(logicalTypeName, id)
                 .map(managedObject -> 
ManagedObjectAndPropertyIfAny.of(managedObject, 
managedObject.getSpecification().getProperty(propertyId)))


Reply via email to