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); + } } }
