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 5fd8a280e17a125071b639a60755ff5956456e00 Author: danhaywood <[email protected]> AuthorDate: Sun Jan 28 15:46:49 2024 +0000 CAUSEWAY-3676: introduces ApiVariant config property --- .../ROOT/pages/2024/2.0.0-RC5/mignotes.adoc | 21 +++++++++++ .../ROOT/pages/2024/2.0.0-RC5/relnotes.adoc | 12 ++++++ .../core/config/CausewayConfiguration.java | 43 ++++++++++++++++++++++ .../graphql/model/domain/GqlvDomainObject.java | 11 ++++-- .../graphql/model/domain/GqlvDomainService.java | 11 +++++- .../viewer/graphql/model/domain/GqlvProperty.java | 17 ++++++++- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/mignotes.adoc b/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/mignotes.adoc index ad2e59b983..9d6f857f6b 100644 --- a/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/mignotes.adoc +++ b/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/mignotes.adoc @@ -52,3 +52,24 @@ Changes: For more details, see link:https://issues.apache.org/jira/browse/CAUSEWAY-3675[CAUSEWAY-3675]. + +== Mavendeps webapp module and the GraphQL viewer (CAUSEWAY-3676) + +The GraphQL viewer is a brand new viewer, automatically exposing your domain object model as a GraphQL API. +For more on this new feature, see the corresponding xref:relnotes::2024/2.0.0-RC5/relnotes.adoc#the-graphql-viewer-causeway-3676[release notes]. + +As part of this work, the `org.apache.causeway.mavendeps:causeway-mavendeps-webapp` convenience module has been updated. +It now references all 3 viewers (Wicket, Restful and GraphQL) as a one-stop shop for bringing in viewers. + +[source,xml] +.pom.xml +---- +<dependency> + <groupId>org.apache.causeway.mavendeps</groupId> + <artifactId>causeway-mavendeps-webapp</artifactId> +</dependency> +---- + +It is still necessary to add in the persistence and security modules. + +For more details, see link:https://issues.apache.org/jira/browse/CAUSEWAY-3676[CAUSEWAY-3676]. diff --git a/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/relnotes.adoc b/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/relnotes.adoc index 4e6c4ebb22..1e78ff4b37 100644 --- a/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/relnotes.adoc +++ b/antora/components/relnotes/modules/ROOT/pages/2024/2.0.0-RC5/relnotes.adoc @@ -6,3 +6,15 @@ NOTE: Not yet released. + +[#the-graphql-viewer-causeway-3676] +== The GraphQL viewer (CAUSEWAY-3676) + +The GraphQL viewer is a brand new viewer which automatically exposes your domain object model as a GraphQL API. + +Built using link:https://spring.io/projects/spring-graphql[Spring for GraphQL], it provides a GraphQL API + +It is important to know that the generated API (in this first release) deliberately -conformant to one of the rueles + +The documentation can be found xref:gqlv:ROOT:about.adoc[here]. +See also link:https://issues.apache.org/jira/browse/CAUSEWAY-3676[CAUSEWAY-3676]. diff --git a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java index 76c7dd4845..ed0964ce86 100644 --- a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java +++ b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java @@ -2346,6 +2346,49 @@ public class CausewayConfiguration { @Data public static class Graphql { + public enum ApiVariant { + /** + * Exposes only a Query API, of properties, collections and safe (query-onl) actions. + * Any actions that mutate the state of the system (in other words are idempotent or non-idempotent) + * are excluded from the API, as is the ability to set properties. + */ + QUERY_ONLY, + /** + * Exposes only a Query API, but relaxes the rule that system state may not be changed by also including + * idempotent and non-idempotent actions as part of the "query" API. Modifiable properties + * can also be set. + * + * <p> + * <b>IMPORTANT</b>: be aware that the resultant API is not compliant with the rules of the + * GraphQL spec; in particular, it violates <a href="https://spec.graphql.org/June2018/#sec-Language.Operations">2.3 Operations</a> which states: + * "query – [is] a read‐only fetch." + * </p> + */ + QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT, + /** + * Exposes an API with Query for query/safe separate queries and field access, with mutating (idempotent + * and non-idempotent) actions instead surfaced as Mutations, as per the + * <a href="https://spec.graphql.org/June2018/#sec-Language.Operations">GraphQL spec</a>. + * + * <p> + * <b>NOTE</b>: this is not currently implemented. + * </p> + */ + QUERY_AND_MUTATIONS, + ; + } + + /** + * Which variant of API to expose: {@link ApiVariant#QUERY_ONLY} (which suppresses any actions that mutate the state of the + * system), or alternatively as {@link ApiVariant#QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT} (which does expose actions that mutate the system but within a query, and so is not spec-compliant), or + * as {@link ApiVariant#QUERY_AND_MUTATIONS} (which also exposes actions that mutate the system but as mutations, and so <i>is</i> spec-compliant. + * + * <p> + * <b>NOTE:</b> {@link ApiVariant#QUERY_AND_MUTATIONS} is not currently implemented. + * </p> + */ + private ApiVariant apiVariant = ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT; + private final MetaData metaData = new MetaData(); @Data public static class MetaData { 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 1498b8a62e..23b6018187 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 @@ -37,10 +37,13 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLObjectType; +import lombok.val; + import static graphql.schema.GraphQLInputObjectField.newInputObjectField; import static graphql.schema.GraphQLInputObjectType.newInputObject; import static graphql.schema.GraphQLNonNull.nonNull; import static graphql.schema.GraphQLObjectType.newObject; +import static org.apache.causeway.core.config.CausewayConfiguration.Viewer.Graphql.ApiVariant.*; /** * Exposes a domain object (view model or entity) via the GQL viewer. @@ -100,11 +103,11 @@ public class GqlvDomainObject implements GqlvAction.Holder, GqlvProperty.Holder, objectSpecification.streamProperties(MixedIn.INCLUDED).forEach(this::addProperty); objectSpecification.streamCollections(MixedIn.INCLUDED).forEach(this::addCollection); - // TODO: pay attention to deploymentType + val variant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); + objectSpecification.streamActions(context.getActionScope(), MixedIn.INCLUDED) - // TODO: for now, we ignore any actions that have any collection parameters - // however, this is supportable in GraphQL, https://chat.openai.com/c/7ca721d5-865a-4765-9f90-5c28046516cd - // .filter(objectAction -> objectAction.getParameters().stream().noneMatch(ObjectActionParameter::isPlural)) + .filter(x -> x.getSemantics().isSafeInNature() || + variant == QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT) .forEach(objectAction -> { actions.put(objectAction.getId(), new GqlvAction(this, objectAction, context)); }); diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java index fe81854d56..a5d2d1e64e 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java @@ -21,11 +21,9 @@ package org.apache.causeway.viewer.graphql.model.domain; import java.util.LinkedHashMap; import java.util.Map; -import org.apache.causeway.core.metamodel.spec.ActionScope; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; -import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter; import org.apache.causeway.viewer.graphql.model.context.Context; import lombok.Getter; @@ -34,10 +32,14 @@ import graphql.schema.FieldCoordinates; 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; +import static org.apache.causeway.core.config.CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT; + /** * Exposes a domain service (view model or entity) via the GQL viewer. */ @@ -83,7 +85,12 @@ public class GqlvDomainService implements GqlvAction.Holder { } private void addActions() { + + val variant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); + objectSpecification.streamActions(context.getActionScope(), MixedIn.INCLUDED) + .filter(x -> x.getSemantics().isSafeInNature() || + variant == QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT) .forEach(this::addAction); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvProperty.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvProperty.java index 0960db17f7..e36d3c826f 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvProperty.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvProperty.java @@ -18,6 +18,7 @@ */ package org.apache.causeway.viewer.graphql.model.domain; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.causeway.viewer.graphql.model.context.Context; @@ -55,6 +56,9 @@ public class GqlvProperty */ private final GqlvPropertyAutoComplete autoComplete; private final GqlvPropertyValidate validate; + /** + * Populated iff the API variant allows for it. + */ private final GqlvPropertySet set; public GqlvProperty( @@ -73,7 +77,14 @@ public class GqlvProperty this.choices = choices.hasChoices() ? choices : null; val autoComplete = new GqlvPropertyAutoComplete(this, context); this.autoComplete = autoComplete.hasAutoComplete() ? autoComplete : null; - this.set = new GqlvPropertySet(this, context); + + val variant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); + if (variant == CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT) { + this.set = new GqlvPropertySet(this, context); + } else { + this.set = null; + } + this.gqlObjectType = gqlObjectTypeBuilder.build(); @@ -134,7 +145,9 @@ public class GqlvProperty autoComplete.addDataFetcher(); } validate.addDataFetcher(); - set.addDataFetcher(); + if (set != null) { + set.addDataFetcher(); + } }
