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 d79b19ed503da0e8beaa6e64d634f66749a23e51
Author: danhaywood <[email protected]>
AuthorDate: Tue Jan 30 06:41:55 2024 +0000

    CAUSEWAY-3676: sketching out introducing mutations
---
 .../graphql/model/domain/GqlvDomainObject.java     |   7 +-
 viewers/graphql/test/src/test/resources/schema.gql |   1 -
 .../integration/GraphQlSourceForCauseway.java      | 123 +++++++++------------
 ...opLevelQuery.java => GqlvTopLevelMutation.java} |  70 ++++++------
 .../graphql/viewer/toplevel/GqlvTopLevelQuery.java |  22 ++--
 5 files changed, 101 insertions(+), 122 deletions(-)

diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
index a4349b4434..1416c7326c 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
@@ -89,14 +89,13 @@ public class GqlvDomainObject implements GqlvAction.Holder, 
GqlvProperty.Holder,
         gqlInputObjectType = inputTypeBuilder.build();
 
         addMembers();
+    }
 
-        // register types
+    public void addTypesInto(GraphQLTypeRegistry graphQLTypeRegistry) {
         gqlObjectType = gqlObjectTypeBuilder.build();
         graphQLTypeRegistry.addTypeIfNotAlreadyPresent(gqlObjectType);
         meta.registerTypesInto(graphQLTypeRegistry);
         graphQLTypeRegistry.addTypeIfNotAlreadyPresent(gqlInputObjectType);
-
-        addDataFetchers();
     }
 
 
@@ -141,7 +140,7 @@ public class GqlvDomainObject implements GqlvAction.Holder, 
GqlvProperty.Holder,
     }
 
 
-    private void addDataFetchers() {
+    public void addDataFetchers() {
         meta.addDataFetchers();
         properties.forEach((id, property) -> property.addDataFetcher());
         collections.forEach((id, collection) -> collection.addDataFetcher());
diff --git a/viewers/graphql/test/src/test/resources/schema.gql 
b/viewers/graphql/test/src/test/resources/schema.gql
index f59319134e..363755e40d 100644
--- a/viewers/graphql/test/src/test/resources/schema.gql
+++ b/viewers/graphql/test/src/test/resources/schema.gql
@@ -26,7 +26,6 @@ type Query {
   causeway_applib_UserMenu: causeway_applib_UserMenu
   causeway_conf_ConfigurationMenu: causeway_conf_ConfigurationMenu
   causeway_security_LogoutMenu: causeway_security_LogoutMenu
-  numServices: Int
   university_admin_AdminMenu: university_admin_AdminMenu
   university_calc_Calculator: university_calc_Calculator
   university_dept_Departments: university_dept_Departments
diff --git 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/integration/GraphQlSourceForCauseway.java
 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/integration/GraphQlSourceForCauseway.java
index e0403c37e3..6ae877b43b 100644
--- 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/integration/GraphQlSourceForCauseway.java
+++ 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/integration/GraphQlSourceForCauseway.java
@@ -19,12 +19,12 @@
 package org.apache.causeway.viewer.graphql.viewer.integration;
 
 import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
 
 import javax.annotation.PostConstruct;
 import javax.inject.Inject;
 
+import org.apache.causeway.viewer.graphql.viewer.toplevel.GqlvTopLevelMutation;
+
 import org.springframework.graphql.execution.GraphQlSource;
 import org.springframework.stereotype.Service;
 
@@ -35,12 +35,10 @@ import 
org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
 import org.apache.causeway.core.config.metamodel.specloader.IntrospectionMode;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import org.apache.causeway.viewer.graphql.applib.types.TypeMapper;
 import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainObject;
-import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainService;
 import org.apache.causeway.viewer.graphql.model.registry.GraphQLTypeRegistry;
 import org.apache.causeway.viewer.graphql.viewer.toplevel.GqlvTopLevelQuery;
 
@@ -53,7 +51,6 @@ import 
graphql.execution.DataFetcherExceptionHandlerParameters;
 import graphql.execution.DataFetcherExceptionHandlerResult;
 import graphql.schema.GraphQLCodeRegistry;
 import graphql.schema.GraphQLSchema;
-import graphql.schema.GraphQLType;
 
 @Service()
 @RequiredArgsConstructor(onConstructor_ = {@Inject})
@@ -83,7 +80,6 @@ public class GraphQlSourceForCauseway implements 
GraphQlSource {
     public GraphQL graphQl() {
         if (graphQL == null) {
             graphQL = GraphQL.newGraphQL(schema())
-//                .instrumentation(new TracingInstrumentation())
                     .defaultDataFetcherExceptionHandler(new 
DataFetcherExceptionHandler() {
                         @Override
                         public DataFetcherExceptionHandlerResult 
onException(DataFetcherExceptionHandlerParameters handlerParameters) {
@@ -91,6 +87,7 @@ public class GraphQlSourceForCauseway implements 
GraphQlSource {
                         }
                     })
                     .queryExecutionStrategy(executionStrategy)
+                    .mutationExecutionStrategy(executionStrategy)
                     .build();
         }
         return graphQL;
@@ -104,94 +101,78 @@ public class GraphQlSourceForCauseway implements 
GraphQlSource {
             throw new IllegalStateException("Metamodel is not fully 
introspected");
         }
 
-        final GraphQLCodeRegistry.Builder codeRegistryBuilder = 
GraphQLCodeRegistry.newCodeRegistry();
+        val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry();
 
-        // add to the top-level query
-        // (and also add behaviour to the child types)
+        // add to the top-level query type and (dependent on configuration) 
the top-level mutation type also
         val topLevelQuery = new GqlvTopLevelQuery(serviceRegistry, 
codeRegistryBuilder);
+        val topLevelMutation =
+                causewayConfiguration.getViewer().getGraphql().getApiVariant() 
== CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_AND_MUTATIONS ?
+                    new GqlvTopLevelMutation(serviceRegistry, 
codeRegistryBuilder)
+                    : null;
 
-        List<ObjectSpecification> objectSpecifications = 
specificationLoader.snapshotSpecifications()
+        val objectSpecifications = specificationLoader.snapshotSpecifications()
                 .distinct((a, b) -> 
a.getLogicalTypeName().equals(b.getLogicalTypeName()))
                 .filter(x -> x.isEntityOrViewModelOrAbstract() || 
x.getBeanSort().isManagedBeanContributing())
                 
.sorted(Comparator.comparing(HasLogicalType::getLogicalTypeName))
                 .toList();
-        objectSpecifications.forEach(objectSpec -> addToSchema(objectSpec, 
topLevelQuery, codeRegistryBuilder));
-
-        topLevelQuery.buildQueryType();
-
-
-        topLevelQuery.addFetchers();
-
-        // finalize the fetcher/mutator code that's been registered
-        val codeRegistry = codeRegistryBuilder.build();
 
+        // add to top-level query
+        val context = new Context(codeRegistryBuilder, bookmarkService, 
specificationLoader, typeMapper, causewayConfiguration, 
causewaySystemEnvironment);
 
-        // build the schema
-        Set<GraphQLType> graphQLTypes = graphQLTypeRegistry.getGraphQLTypes();
-        return GraphQLSchema.newSchema()
-                .query(topLevelQuery.getQueryType())
-                .additionalTypes(graphQLTypes)
-                .codeRegistry(codeRegistry)
-                .build();
-    }
+        objectSpecifications.forEach(objectSpec -> {
+            switch (objectSpec.getBeanSort()) {
 
-    private void addToSchema(
-            final ObjectSpecification objectSpec,
-            final GqlvTopLevelQuery gqlvTopLevelQuery,
-            final GraphQLCodeRegistry.Builder codeRegistryBuilder) {
+                case MANAGED_BEAN_CONTRIBUTING: // @DomainService
 
-        Context context = new Context(codeRegistryBuilder, bookmarkService, 
specificationLoader, typeMapper, causewayConfiguration, 
causewaySystemEnvironment);
+                    
serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName())
+                        .ifPresent(servicePojo ->
+                        {
+                            topLevelQuery.addDomainServiceTo(objectSpec, 
servicePojo, context);
 
-        switch (objectSpec.getBeanSort()) {
+                        });
+                    break;
 
-            case MANAGED_BEAN_CONTRIBUTING: // @DomainService
+                case ABSTRACT:
+                case VIEW_MODEL: // @DomainObject(nature=VIEW_MODEL)
+                case ENTITY:     // @DomainObject(nature=ENTITY)
 
-                addDomainServiceToTopLevelQuery(objectSpec, gqlvTopLevelQuery, 
context);
-                break;
+                    val gqlvDomainObject = new GqlvDomainObject(objectSpec, 
context, objectManager, graphQLTypeRegistry);
+                    gqlvDomainObject.addTypesInto(graphQLTypeRegistry);
+                    gqlvDomainObject.addDataFetchers();
 
-            case ABSTRACT:
-                // TODO: App interface should map to gql interfaces?
-            case VIEW_MODEL: // @DomainObject(nature=VIEW_MODEL)
-            case ENTITY:     // @DomainObject(nature=ENTITY)
+                    break;
 
-                new GqlvDomainObject(objectSpec, context, objectManager, 
graphQLTypeRegistry);
+                case MANAGED_BEAN_NOT_CONTRIBUTING: // a @Service or 
@Component ... ignore
+                case MIXIN:
+                case VALUE:
+                case COLLECTION:
+                case VETOED:
+                case UNKNOWN:
+                    break;
+            }
+        });
+        topLevelQuery.buildQueryType();
 
-                break;
+        if (topLevelMutation != null) {
 
-            case MANAGED_BEAN_NOT_CONTRIBUTING: // a @Service or @Component 
... ignore
-            case MIXIN:
-            case VALUE:
-            case COLLECTION:
-            case VETOED:
-            case UNKNOWN:
-                break;
+            topLevelMutation.buildMutationType();
+            topLevelMutation.addFetchers();
         }
-    }
-
-    public void addDomainServiceToTopLevelQuery(
-            final ObjectSpecification objectSpec,
-            final GqlvTopLevelQuery topLevelQueryStructure,
-            final Context context) {
 
-        serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName())
-            .ifPresent(servicePojo ->
-                addDomainServiceToTopLevelQuery(servicePojo, objectSpec, 
topLevelQueryStructure, context));
-    }
-
-    private void addDomainServiceToTopLevelQuery(
-            final Object servicePojo,
-            final ObjectSpecification objectSpec,
-            final GqlvTopLevelQuery topLevelQuery,
-            final Context context) {
 
-        val domainService = new GqlvDomainService(objectSpec, servicePojo, 
context);
+        // finalize the fetcher/mutator code that's been registered
+        val codeRegistry = codeRegistryBuilder.build();
 
-        boolean actionsAdded = domainService.hasActions();
-        if (actionsAdded) {
-            topLevelQuery.addFieldFor(domainService, 
context.codeRegistryBuilder);
+        // build the schema
+        val schemaBuilder = GraphQLSchema.newSchema()
+                .query(topLevelQuery.getQueryType())
+                .additionalTypes(graphQLTypeRegistry.getGraphQLTypes())
+                .codeRegistry(codeRegistry);
+        if (topLevelMutation != null) {
+            schemaBuilder.mutation(topLevelMutation.getObjectType());
         }
-
-
+        return schemaBuilder
+                .build();
     }
 
 
diff --git 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelMutation.java
similarity index 50%
copy from 
viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
copy to 
viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelMutation.java
index 36b6f983dc..c04fa400b2 100644
--- 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
+++ 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelMutation.java
@@ -1,10 +1,5 @@
 package org.apache.causeway.viewer.graphql.viewer.toplevel;
 
-import org.apache.causeway.applib.services.registry.ServiceRegistry;
-import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainService;
-
-import lombok.Getter;
-
 import graphql.Scalars;
 import graphql.schema.DataFetcher;
 import graphql.schema.FieldCoordinates;
@@ -16,73 +11,78 @@ import static graphql.schema.FieldCoordinates.coordinates;
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 import static graphql.schema.GraphQLObjectType.newObject;
 
-public class GqlvTopLevelQuery {
+import org.apache.causeway.applib.services.registry.ServiceRegistry;
+import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainService;
+
+import lombok.Getter;
+
+public class GqlvTopLevelMutation {
 
     private final ServiceRegistry serviceRegistry;
     private final GraphQLCodeRegistry.Builder codeRegistryBuilder;
 
-    @Getter final GraphQLObjectType.Builder queryBuilder;
+    @Getter final GraphQLObjectType.Builder gqlObjectBuilder;
 
     @Getter private GraphQLFieldDefinition numServicesField;
 
     /**
-     * Built using {@link #buildQueryType()}
+     * Built using {@link #buildMutationType()}
      */
-    private GraphQLObjectType queryType;
+    private GraphQLObjectType objectType;
 
 
-    public GqlvTopLevelQuery(
+    public GqlvTopLevelMutation(
             final ServiceRegistry serviceRegistry,
             final GraphQLCodeRegistry.Builder codeRegistryBuilder) {
         this.serviceRegistry = serviceRegistry;
         this.codeRegistryBuilder = codeRegistryBuilder;
-        queryBuilder = newObject().name("Query");
+        gqlObjectBuilder = newObject().name("Mutation");
 
         numServicesField = newFieldDefinition()
                 .name("numServices")
                 .type(Scalars.GraphQLInt)
                 .build();
-        queryBuilder.field(numServicesField);
+        gqlObjectBuilder.field(numServicesField);
     }
 
 
 
-    public GraphQLObjectType buildQueryType() {
-        if (queryType != null) {
-            throw new IllegalStateException("QueryType has already been 
built");
+    public GraphQLObjectType buildMutationType() {
+        if (objectType != null) {
+            throw new IllegalStateException("Mutation type has already been 
built");
         }
-        return queryType = queryBuilder.build();
+        return objectType = gqlObjectBuilder.build();
     }
 
     /**
      *
-     * @see #buildQueryType()
+     * @see #buildMutationType()
      */
-    public GraphQLObjectType getQueryType() {
-        if (queryType == null) {
-            throw new IllegalStateException("QueryType has not yet been 
built");
+    public GraphQLObjectType getObjectType() {
+        if (objectType == null) {
+            throw new IllegalStateException("Mutation type has not yet been 
built");
         }
-        return queryType;
+        return objectType;
     }
 
-    public void addFieldFor(
-            final GqlvDomainService domainService,
-            final GraphQLCodeRegistry.Builder codeRegistryBuilder) {
-
-        GraphQLFieldDefinition topLevelQueryField = 
domainService.createTopLevelQueryField();
-        queryBuilder.field(topLevelQueryField);
-
-        codeRegistryBuilder.dataFetcher(
-                // TODO: it would be nice to make these typesafe...
-                FieldCoordinates.coordinates("Query", 
topLevelQueryField.getName()),
-                (DataFetcher<Object>) environment -> 
domainService.getServicePojo());
-
-    }
+//    public void addFieldFor(
+//            final GqlvDomainService domainService,
+//            final GraphQLCodeRegistry.Builder codeRegistryBuilder) {
+//
+//        GraphQLFieldDefinition topLevelQueryField = 
domainService.createTopLevelQueryField();
+//        gqlObjectBuilder.field(topLevelQueryField);
+//
+//        codeRegistryBuilder.dataFetcher(
+//                // TODO: it would be nice to make these typesafe...
+//                FieldCoordinates.coordinates("Mutation", 
topLevelQueryField.getName()),
+//                (DataFetcher<Object>) environment -> 
domainService.getServicePojo());
+//
+//    }
 
     public void addFetchers() {
         codeRegistryBuilder
             .dataFetcher(
-                coordinates(getQueryType(), getNumServicesField()),
+                coordinates(getObjectType(), getNumServicesField()),
                 (DataFetcher<Object>) environment -> 
this.serviceRegistry.streamRegisteredBeans().count());
     }
 
diff --git 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
index 36b6f983dc..71eb279b5d 100644
--- 
a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
+++ 
b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/toplevel/GqlvTopLevelQuery.java
@@ -1,6 +1,8 @@
 package org.apache.causeway.viewer.graphql.viewer.toplevel;
 
 import org.apache.causeway.applib.services.registry.ServiceRegistry;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainService;
 
 import lombok.Getter;
@@ -12,6 +14,8 @@ import graphql.schema.GraphQLCodeRegistry;
 import graphql.schema.GraphQLFieldDefinition;
 import graphql.schema.GraphQLObjectType;
 
+import lombok.val;
+
 import static graphql.schema.FieldCoordinates.coordinates;
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 import static graphql.schema.GraphQLObjectType.newObject;
@@ -23,7 +27,6 @@ public class GqlvTopLevelQuery {
 
     @Getter final GraphQLObjectType.Builder queryBuilder;
 
-    @Getter private GraphQLFieldDefinition numServicesField;
 
     /**
      * Built using {@link #buildQueryType()}
@@ -38,11 +41,6 @@ public class GqlvTopLevelQuery {
         this.codeRegistryBuilder = codeRegistryBuilder;
         queryBuilder = newObject().name("Query");
 
-        numServicesField = newFieldDefinition()
-                .name("numServices")
-                .type(Scalars.GraphQLInt)
-                .build();
-        queryBuilder.field(numServicesField);
     }
 
 
@@ -79,11 +77,13 @@ public class GqlvTopLevelQuery {
 
     }
 
-    public void addFetchers() {
-        codeRegistryBuilder
-            .dataFetcher(
-                coordinates(getQueryType(), getNumServicesField()),
-                (DataFetcher<Object>) environment -> 
this.serviceRegistry.streamRegisteredBeans().count());
+    public void addDomainServiceTo(ObjectSpecification objectSpec, Object 
servicePojo, Context context) {
+        val domainService = new GqlvDomainService(objectSpec, servicePojo, 
context);
+
+        boolean actionsAdded = domainService.hasActions();
+        if (actionsAdded) {
+            addFieldFor(domainService, context.codeRegistryBuilder);
+        }
     }
 
 }

Reply via email to