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 28ec90f8e16c377ea55e7d36c60e7687aa31ae66 Author: danhaywood <[email protected]> AuthorDate: Thu Mar 14 08:49:08 2024 +0000 CAUSEWAY-3676 : adds ability to filter GraphQL by view model --- .../applib/services/metamodel/BeanSort.java | 3 +- .../applib/services/swagger/Visibility.java | 4 +- .../core/config/CausewayConfiguration.java | 52 +++++++++++++++++++--- .../core/metamodel/spec/ObjectSpecification.java | 23 ++++++++++ .../domain/common/query/CommonDomainObject.java | 24 +++++++++- .../common/query/CommonTopLevelQueryAbstract.java | 23 +++++++--- .../integration/GraphQlSourceForCauseway.java | 1 - 7 files changed, 114 insertions(+), 16 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java index 5490a6e4bc..c87a758b41 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java @@ -129,7 +129,7 @@ public enum BeanSort { return this == UNKNOWN; } - // -- HIGER LEVEL PREDICATES + // -- HIGHER LEVEL PREDICATES public boolean isToBeIntrospected() { @@ -150,5 +150,6 @@ public enum BeanSort { } + // ... } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/swagger/Visibility.java b/api/applib/src/main/java/org/apache/causeway/applib/services/swagger/Visibility.java index 918ccd3051..dc4d78175a 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/swagger/Visibility.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/swagger/Visibility.java @@ -46,7 +46,7 @@ public enum Visibility { * The generated swagger spec is therefore restricted only to include only * view models ({@link DomainObject#nature()} of * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL}) - * and to REST domain services ({@link DomainService#nature()} of + * and to Web API domain services ({@link DomainService#nature()} of * {@link NatureOfService#WEB_API}). Exposing entities also would couple the * REST client too deeply to the backend implementation. * </p> @@ -60,7 +60,7 @@ public enum Visibility { * This visibility level removes all constraints and so includes the * specifications of domain entities as well as view models. This is * perfectly acceptable where the team developing the REST client is the - * same as the team developing the backend service ... the use of the REST + * same as the team developing the backend service ... the use of the Web * API between the client and server is a private implementation detail of * the application. * </p> 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 b2dea70284..629157f69f 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 @@ -57,6 +57,8 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.apache.causeway.applib.annotation.NatureOfService; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; @@ -2364,7 +2366,7 @@ public class CausewayConfiguration { * * <p> * Optionally, fields for Scenario (given/when/then) testing may also be added if the - * {@link #isEnableScenarioTestingForRich()} config property is set. + * {@link Schema.Rich#isEnableScenarioTesting()} config property is set. * </p> * <p> * Suitable for clients where the application logic and state remains in the backend, within the @@ -2374,8 +2376,8 @@ public class CausewayConfiguration { RICH_ONLY, /** * Exposes both the simple and rich schemas, for the query have each under a field as defined by - * {@link #getTopLevelFieldNameForSimple()} (by default "simple") and - * {@link #getTopLevelFieldNameForRich()} (by default "rich"). + * {@link Schema.Simple#getTopLevelFieldName()} (by default "simple") and + * {@link Schema.Rich#getTopLevelFieldName()} (by default "rich"). * * <p> * For mutations, use the <i>simple</i> schema types. @@ -2384,8 +2386,8 @@ public class CausewayConfiguration { SIMPLE_AND_RICH, /** * Exposes both the simple and rich schemas, for the query have each under a field as defined by - * {@link #getTopLevelFieldNameForSimple()} (by default "simple") and - * {@link #getTopLevelFieldNameForRich()} (by default "rich"). + * {@link Schema.Simple#getTopLevelFieldName()} (by default "simple") and + * {@link Schema.Rich#getTopLevelFieldName()} (by default "rich"). * * <p> * For mutations, use the <i>rich</i> schema types. @@ -2490,6 +2492,46 @@ public class CausewayConfiguration { */ private ApiVariant apiVariant = ApiVariant.QUERY_AND_MUTATIONS; + /** + * Specifies which elements of the metamodel are included within the generated + * GraphQL spec. + * + * @since 1.x {@index} + */ + public enum ApiScope { + + /** + * The generated GraphQL spec is restricted only to include only + * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL view model}s. + * + * <p> + * Applicable when the GraphQL API is in use by third-party clients, ie public use and not + * under the control of the authors of the backend Apache Causeway application. + * Exposing entities also would couple the GraphQL client too deeply to the backend implementation. + * </p> + */ + VIEW_MODELS, + /** + * The generated GraphQL spec is not restricted, includes both + * {@link org.apache.causeway.applib.annotation.Nature#ENTITY domain entities} as well as + * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL view model}s. + * + * <p> + * This is perfectly acceptable where the team developing the GraphQL client is the + * same as the team developing the backend service ... the use of the Web + * API between the client and server is a private implementation detail of + * the application. + * </p> + */ + ALL, + ; + } + + /** + * Which domain objects to include the GraphQL schema. + */ + private ApiScope apiScope = ApiScope.ALL; + private final MetaData metaData = new MetaData(); @Data public static class MetaData { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java index 8680f5e289..01acac6fa5 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java @@ -479,11 +479,34 @@ extends /** * Includes abstract types that have * {@link ViewModelFacet} or {@link EntityFacet}. + * + * @see #isViewModel() + * @see #isEntity() */ default boolean isEntityOrViewModel() { return isViewModel() || isEntity(); } + /** + * @see #isViewModel() + * @see #isValue() + */ + default boolean isViewModelOrValue() { + return isViewModel() || isValue(); + } + + /** + * @see #isViewModel() + * @see #isValue() + * @see #isVoid() + */ + default boolean isViewModelOrValueOrVoid() { + return isViewModel() || isValue() || isVoid(); + } + + /** + * @see #getBeanSort() + */ default boolean isEntityOrViewModelOrAbstract() { // optimized, no need to check facets return getBeanSort().isViewModel() diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java index bb06f934fe..52c7a8d15f 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java @@ -31,9 +31,13 @@ import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; import static graphql.schema.GraphQLInputObjectField.newInputObjectField; import static graphql.schema.GraphQLInputObjectType.newInputObject; +import org.apache.causeway.core.config.CausewayConfiguration; 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.ObjectAssociation; +import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.causeway.viewer.graphql.model.context.Context; import org.apache.causeway.viewer.graphql.model.domain.Environment; import org.apache.causeway.viewer.graphql.model.domain.Element; @@ -158,16 +162,34 @@ public class CommonDomainObject private void addMembers() { - objectSpecification.streamProperties(MixedIn.INCLUDED) + .filter(this::inApiScope) .forEach(prop -> properties.add(addChildFieldFor(schemaStrategy.newProperty(this, prop, context)))); objectSpecification.streamCollections(MixedIn.INCLUDED) + .filter(this::inApiScope) .forEach(coll -> collections.add(addChildFieldFor(schemaStrategy.newCollection(this, coll, context)))); objectSpecification.streamActions(context.getActionScope(), MixedIn.INCLUDED) + .filter((this::inApiScope)) .filter(act -> schemaStrategy.shouldInclude(graphqlConfiguration.getApiVariant(), act)) .forEach(act -> actions.add(addChildFieldFor(schemaStrategy.newAction(this, act, context)))); } + private boolean inApiScope(ObjectAction act) { + if (graphqlConfiguration.getApiScope() == CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) { + return true; + } + val returnType = act.getElementType(); + return returnType.isViewModelOrValueOrVoid() && + act.getParameterTypes().stream().allMatch(ObjectSpecification::isViewModelOrValue); + } + + private boolean inApiScope(final ObjectAssociation objAssoc) { + if (graphqlConfiguration.getApiScope() == CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) { + return true; + } + return objAssoc.getElementType().isViewModelOrValue(); + } + @SuppressWarnings("unused") private ActionScope determineActionScope() { return context.causewaySystemEnvironment.getDeploymentType().isProduction() diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java index 632ca78074..f2b6454043 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java @@ -23,6 +23,7 @@ import java.util.List; import graphql.schema.DataFetchingEnvironment; +import org.apache.causeway.core.config.CausewayConfiguration; 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.Element; @@ -48,17 +49,19 @@ public abstract class CommonTopLevelQueryAbstract super(schemaStrategy.getSchemaType().name() + "Schema", context); this.schemaStrategy = schemaStrategy; + boolean includeEntities = context.causewayConfiguration.getViewer().getGraphql().getApiScope() == CausewayConfiguration.Viewer.Graphql.ApiScope.ALL; context.objectSpecifications().forEach(objectSpec -> { switch (objectSpec.getBeanSort()) { case ABSTRACT: - case VIEW_MODEL: // @DomainObject(nature=VIEW_MODEL) - case ENTITY: // @DomainObject(nature=ENTITY) - - val gqlvDomainObject = schemaStrategy.domainObjectFor(objectSpec, context); - addChildField(gqlvDomainObject.newField()); - domainObjects.add(gqlvDomainObject); + case VIEW_MODEL: + addDomainObject(objectSpec, schemaStrategy, context); break; + case ENTITY: + if (includeEntities) { + addDomainObject(objectSpec, schemaStrategy, context); + } + } }); @@ -73,7 +76,15 @@ public abstract class CommonTopLevelQueryAbstract break; } }); + } + private void addDomainObject( + final ObjectSpecification objectSpec, + final SchemaStrategy schemaStrategy, + final Context context) { + val gqlvDomainObject = schemaStrategy.domainObjectFor(objectSpec, context); + addChildField(gqlvDomainObject.newField()); + domainObjects.add(gqlvDomainObject); } public static List<ObjectSpecification> superclassesOf(final ObjectSpecification objectSpecification) { 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 4b6d7922b1..ab7d670128 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 @@ -43,7 +43,6 @@ import org.apache.causeway.viewer.graphql.model.toplevel.BothTopLevelQuery; import lombok.val; @Service() -//@RequiredArgsConstructor(onConstructor_ = {@Inject}) public class GraphQlSourceForCauseway implements GraphQlSource { private final CausewayConfiguration causewayConfiguration;
