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 d5a6515397e5bdd0efea22338485a083e60ec5c9 Author: danhaywood <[email protected]> AuthorDate: Wed Feb 14 21:07:15 2024 +0000 CAUSEWAY-3676: adds support for retrieving blob bytes via rest controller; also support for obtaining multiple references from a choices or autoComplete etc --- .../graphql/model/domain/GqlvAbstractCustom.java | 4 + .../viewer/graphql/model/domain/GqlvAction.java | 17 +--- .../graphql/model/domain/GqlvAssociationGet.java | 4 +- .../graphql/model/domain/GqlvMetaSaveAs.java | 14 +++- .../viewer/graphql/model/domain/GqlvProperty.java | 33 +++++--- .../graphql/model/domain/GqlvPropertyGetBlob.java | 90 ++++++++++++++++++++++ ...onGet.java => GqlvPropertyGetBlobAbstract.java} | 60 +++++++-------- ...taSaveAs.java => GqlvPropertyGetBlobBytes.java} | 38 ++++----- ...aveAs.java => GqlvPropertyGetBlobMimeType.java} | 30 ++------ ...etaSaveAs.java => GqlvPropertyGetBlobName.java} | 32 ++------ .../viewer/graphql/model/domain/TypeNames.java | 4 + ...d_department_and_add_staff_member_choices._.gql | 34 ++++++++ ...ment_and_add_staff_member_choices.approved.json | 52 +++++++++++++ ...department_and_add_staff_members.choices._.gql} | 0 .../queryandmutations/Department_IntegTest.java | 9 +++ .../Staff_IntegTest.list_all_staff_members._.gql | 6 +- ..._IntegTest.list_all_staff_members.approved.json | 30 ++++++-- viewers/graphql/test/src/test/resources/schema.gql | 28 ++++++- viewers/graphql/viewer/pom.xml | 4 + .../graphql/viewer/src/main/java/module-info.java | 2 + .../viewer/controller/BlobBytesController.java | 80 +++++++++++++++++++ 21 files changed, 433 insertions(+), 138 deletions(-) diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAbstractCustom.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAbstractCustom.java index 9847453041..479b7cc62e 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAbstractCustom.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAbstractCustom.java @@ -59,6 +59,10 @@ public abstract class GqlvAbstractCustom extends GqlvAbstract implements Parent return gqlObjectType != null; } + protected final void addChildFieldFor(GqlvAbstract hasField) { + addChildField(hasField.getField()); + } + protected final void addChildField(GraphQLFieldDefinition childField) { if (isBuilt()) { return; diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java index d4f037dcb7..fa8ef68e2c 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java @@ -72,22 +72,13 @@ public class GqlvAction final Context context) { super(holder, objectAction, TypeNames.actionTypeNameFor(holder.getObjectSpecification(), objectAction), context); - this.hidden = new GqlvMemberHidden<>(this, context); - addChildField(hidden.getField()); - - this.disabled = new GqlvMemberDisabled<>(this, context); - addChildField(disabled.getField()); - - this.validate = new GqlvActionValidity(this, context); - addChildField(validate.getField()); + addChildFieldFor(this.hidden = new GqlvMemberHidden<>(this, context)); + addChildFieldFor(this.disabled = new GqlvMemberDisabled<>(this, context)); + addChildFieldFor(this.validate = new GqlvActionValidity(this, context)); val variant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); if (objectAction.getSemantics().isSafeInNature() || variant == QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT) { - this.invoke = new GqlvActionInvoke(this, context); - GraphQLFieldDefinition invokeField = this.invoke.getField(); - if (invokeField != null) { - addChildField(invokeField); - } + addChildFieldFor(this.invoke = new GqlvActionInvoke(this, context)); } else { this.invoke = null; } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java index 2acd561b72..dbb63d0c3c 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java @@ -56,10 +56,10 @@ public abstract class GqlvAssociationGet<T extends ObjectAssociation> extends Gq abstract GraphQLOutputType outputTypeFor(Holder<T> holder); @Override - protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment) { + protected Object fetchData(final DataFetchingEnvironment environment) { // TODO: introduce evaluator - val sourcePojo = BookmarkedPojo.sourceFrom(dataFetchingEnvironment); + val sourcePojo = BookmarkedPojo.sourceFrom(environment); val sourcePojoClass = sourcePojo.getClass(); val objectSpecification = context.specificationLoader.loadSpecification(sourcePojoClass); diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java index 9f2e019396..62d54a1567 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java @@ -18,10 +18,13 @@ */ package org.apache.causeway.viewer.graphql.model.domain; +import graphql.GraphQLContext; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLArgument; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.causeway.viewer.graphql.model.context.Context; import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; @@ -46,7 +49,16 @@ public class GqlvMetaSaveAs extends GqlvAbstract { protected Object fetchData(DataFetchingEnvironment environment) { String ref = environment.getArgument("ref"); GqlvMeta.Fetcher source = environment.getSource(); - environment.getGraphQlContext().put(keyFor(ref), new BookmarkedPojo(source.bookmark(), context.bookmarkService)); + String originalKey = keyFor(ref); + GraphQLContext graphQlContext = environment.getGraphQlContext(); + + // we ensure the key hasn't been used already + int i = 2; // we start at 2 deliberately, so save "cust", "cust-2", "cust-3" ... etc if there is a clash + String key = originalKey; + while (graphQlContext.hasKey(key)) { + key = originalKey + "-" + (i++); + } + graphQlContext.put(key, new BookmarkedPojo(source.bookmark(), context.bookmarkService)); return ref; } 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 3cd351d1b8..7e82bd35fc 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 @@ -21,6 +21,8 @@ package org.apache.causeway.viewer.graphql.model.domain; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLFieldDefinition; +import org.apache.causeway.applib.value.Blob; +import org.apache.causeway.applib.value.Clob; import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; @@ -32,17 +34,18 @@ import lombok.val; public class GqlvProperty extends GqlvAssociation<OneToOneAssociation, GqlvMember.Holder> implements GqlvMemberHidden.Holder<OneToOneAssociation>, - GqlvMemberDisabled.Holder<OneToOneAssociation>, - GqlvPropertyGet.Holder, - GqlvPropertyChoices.Holder, - GqlvPropertyAutoComplete.Holder, - GqlvPropertyValidate.Holder, - GqlvPropertySet.Holder, - GqlvAssociationDatatype.Holder<OneToOneAssociation> { + GqlvMemberDisabled.Holder<OneToOneAssociation>, + GqlvPropertyGet.Holder, + GqlvPropertyChoices.Holder, + GqlvPropertyAutoComplete.Holder, + GqlvPropertyValidate.Holder, + GqlvPropertySet.Holder, + GqlvAssociationDatatype.Holder<OneToOneAssociation>, + GqlvPropertyGetBlob.Holder { private final GqlvMemberHidden<OneToOneAssociation> hidden; private final GqlvMemberDisabled<OneToOneAssociation> disabled; - private final GqlvPropertyGet get; + private final GqlvAbstract get; /** * Populated iff there are choices */ @@ -71,7 +74,11 @@ public class GqlvProperty this.disabled = new GqlvMemberDisabled<>(this, context); addChildField(disabled.getField()); - this.get = new GqlvPropertyGet(this, context); + if (isBlob()) { + this.get = new GqlvPropertyGetBlob(this, context); + } else { + this.get = new GqlvPropertyGet(this, context); + } addChildField(get.getField()); this.validate = new GqlvPropertyValidate(this, context); @@ -107,6 +114,14 @@ public class GqlvProperty buildObjectTypeAndField(oneToOneAssociation.getId()); } + private boolean isBlob() { + return getOneToOneAssociation().getElementType().getCorrespondingClass() == Blob.class; + } + + private boolean isClob() { + return getOneToOneAssociation().getElementType().getCorrespondingClass() == Clob.class; + } + public void addGqlArgument( final OneToOneAssociation oneToOneAssociation, final GraphQLFieldDefinition.Builder builder, diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java new file mode 100644 index 0000000000..bb9e3d830d --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.viewer.graphql.model.domain; + +import graphql.schema.DataFetchingEnvironment; + +import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; + +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; +import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; +import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectAssociationProvider; +import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectSpecificationProvider; + +public class GqlvPropertyGetBlob + extends GqlvAbstractCustom + implements GqlvPropertyGetBlobBytes.Holder +{ + + final Holder holder; + final GqlvPropertyGetBlobBytes blobName; + final GqlvPropertyGetBlobMimeType blobMimeType; + final GqlvPropertyGetBlobName blobBytes; + + public GqlvPropertyGetBlob( + final Holder holder, + final Context context) { + super(TypeNames.propertyBlobTypeNameFor(holder.getObjectSpecification(), holder.getObjectMember()), context); + this.holder = holder; + + addChildFieldFor(blobName = new GqlvPropertyGetBlobBytes(this, context)); + addChildFieldFor(blobMimeType = new GqlvPropertyGetBlobMimeType(this, context)); + addChildFieldFor(blobBytes = new GqlvPropertyGetBlobName(this, context)); + + setField(newFieldDefinition() + .name("get") + .type(buildObjectType()) + .build()); + } + + @Override + protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment) { + return BookmarkedPojo.sourceFrom(dataFetchingEnvironment, context); + } + + @Override + protected void addDataFetchersForChildren() { + blobName.addDataFetcher(this); + blobMimeType.addDataFetcher(this); + blobBytes.addDataFetcher(this); + } + + @Override + public OneToOneAssociation getObjectAssociation() { + return holder.getObjectAssociation(); + } + + @Override + public OneToOneAssociation getObjectMember() { + return holder.getObjectMember(); + } + + @Override + public ObjectSpecification getObjectSpecification() { + return holder.getObjectSpecification(); + } + + public interface Holder + extends ObjectSpecificationProvider, + ObjectAssociationProvider<OneToOneAssociation> { + + } +} diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobAbstract.java similarity index 55% copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobAbstract.java index 2acd561b72..b2a9d79136 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAssociationGet.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobAbstract.java @@ -18,48 +18,39 @@ */ package org.apache.causeway.viewer.graphql.model.domain; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLOutputType; +import java.util.Optional; +import java.util.function.Function; -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import graphql.Scalars; +import graphql.schema.DataFetchingEnvironment; +import org.apache.causeway.applib.value.Blob; import org.apache.causeway.core.metamodel.object.ManagedObject; -import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation; import org.apache.causeway.viewer.graphql.model.context.Context; import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; -import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectAssociationProvider; -import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectSpecificationProvider; + +import graphql.schema.GraphQLFieldDefinition; import lombok.val; -public abstract class GqlvAssociationGet<T extends ObjectAssociation> extends GqlvAbstract { +public abstract class GqlvPropertyGetBlobAbstract extends GqlvAbstract { - final Holder<T> holder; + final Holder holder; - public GqlvAssociationGet( - final Holder<T> holder, - final Context context) { + public GqlvPropertyGetBlobAbstract( + final Holder holder, + final Context context, String name) { super(context); this.holder = holder; - GraphQLOutputType type = outputTypeFor(holder); - if (type != null) { - val fieldBuilder = newFieldDefinition() - .name("get") - .type(type); - setField(fieldBuilder.build()); - } else { - setField(null); - } + setField(GraphQLFieldDefinition.newFieldDefinition() + .name(name) + .type(Scalars.GraphQLString) + .build()); } - abstract GraphQLOutputType outputTypeFor(Holder<T> holder); - - @Override - protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment) { - - // TODO: introduce evaluator - val sourcePojo = BookmarkedPojo.sourceFrom(dataFetchingEnvironment); + protected Object fetchDataFromBlob(DataFetchingEnvironment environment, Function<Blob, ?> mapper) { + val sourcePojo = BookmarkedPojo.sourceFrom(environment); val sourcePojoClass = sourcePojo.getClass(); val objectSpecification = context.specificationLoader.loadSpecification(sourcePojoClass); @@ -72,14 +63,15 @@ public abstract class GqlvAssociationGet<T extends ObjectAssociation> extends Gq val managedObject = ManagedObject.adaptSingular(objectSpecification, sourcePojo); val resultManagedObject = association.get(managedObject); - return resultManagedObject != null - ? resultManagedObject.getPojo() - : null; + return Optional.ofNullable(resultManagedObject) + .map(ManagedObject::getPojo) + .filter(Blob.class::isInstance) + .map(Blob.class::cast) + .map(mapper) + .orElse(null); } - public interface Holder<T extends ObjectAssociation> - extends ObjectSpecificationProvider, - ObjectAssociationProvider<T> { - + public interface Holder extends GqlvPropertyGet.Holder { } + } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java similarity index 55% copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java index 9f2e019396..5e2246d39c 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java @@ -18,40 +18,32 @@ */ package org.apache.causeway.viewer.graphql.model.domain; -import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLArgument; -import org.apache.causeway.viewer.graphql.model.context.Context; -import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; +import lombok.val; -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import java.util.Optional; -public class GqlvMetaSaveAs extends GqlvAbstract { +import org.apache.causeway.applib.services.bookmark.Bookmark; +import org.apache.causeway.viewer.graphql.model.context.Context; +import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; - public GqlvMetaSaveAs(final Context context) { - super(context); +public class GqlvPropertyGetBlobBytes extends GqlvPropertyGetBlobAbstract { - setField(newFieldDefinition() - .name("saveAs") - .type(Scalars.GraphQLString) - .argument(new GraphQLArgument.Builder() - .name("ref") - .type(Scalars.GraphQLString) - ) - .build()); + public GqlvPropertyGetBlobBytes( + final Holder holder, + final Context context) { + super(holder, context, "bytes"); } @Override protected Object fetchData(DataFetchingEnvironment environment) { - String ref = environment.getArgument("ref"); - GqlvMeta.Fetcher source = environment.getSource(); - environment.getGraphQlContext().put(keyFor(ref), new BookmarkedPojo(source.bookmark(), context.bookmarkService)); - return ref; - } + val sourcePojo = BookmarkedPojo.sourceFrom(environment); + + Optional<Bookmark> bookmarkIfAny = context.bookmarkService.bookmarkFor(sourcePojo); + return bookmarkIfAny.map(x -> String.format( + "///%s/object/%s:%s/%s/blobBytes", "graphql", x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); - public static String keyFor(String ref) { - return GqlvMetaSaveAs.class.getName() + "#" + ref; } } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobMimeType.java similarity index 51% copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobMimeType.java index 9f2e019396..f80bdf3284 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobMimeType.java @@ -18,40 +18,22 @@ */ package org.apache.causeway.viewer.graphql.model.domain; -import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLArgument; import org.apache.causeway.viewer.graphql.model.context.Context; -import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +public class GqlvPropertyGetBlobMimeType extends GqlvPropertyGetBlobAbstract { -public class GqlvMetaSaveAs extends GqlvAbstract { + public GqlvPropertyGetBlobMimeType( + final Holder holder, + final Context context) { + super(holder, context, "mimeType"); - public GqlvMetaSaveAs(final Context context) { - super(context); - - setField(newFieldDefinition() - .name("saveAs") - .type(Scalars.GraphQLString) - .argument(new GraphQLArgument.Builder() - .name("ref") - .type(Scalars.GraphQLString) - ) - .build()); } @Override protected Object fetchData(DataFetchingEnvironment environment) { - String ref = environment.getArgument("ref"); - GqlvMeta.Fetcher source = environment.getSource(); - environment.getGraphQlContext().put(keyFor(ref), new BookmarkedPojo(source.bookmark(), context.bookmarkService)); - return ref; - } - - public static String keyFor(String ref) { - return GqlvMetaSaveAs.class.getName() + "#" + ref; + return fetchDataFromBlob(environment, blob -> blob.getMimeType().toString()); } } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobName.java similarity index 51% copy from viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java copy to viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobName.java index 9f2e019396..63180d7c17 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMetaSaveAs.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobName.java @@ -18,40 +18,22 @@ */ package org.apache.causeway.viewer.graphql.model.domain; -import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLArgument; +import org.apache.causeway.applib.value.Blob; import org.apache.causeway.viewer.graphql.model.context.Context; -import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +public class GqlvPropertyGetBlobName extends GqlvPropertyGetBlobAbstract { -public class GqlvMetaSaveAs extends GqlvAbstract { - - public GqlvMetaSaveAs(final Context context) { - super(context); - - setField(newFieldDefinition() - .name("saveAs") - .type(Scalars.GraphQLString) - .argument(new GraphQLArgument.Builder() - .name("ref") - .type(Scalars.GraphQLString) - ) - .build()); + public GqlvPropertyGetBlobName( + final Holder holder, + final Context context) { + super(holder, context, "name"); } @Override protected Object fetchData(DataFetchingEnvironment environment) { - String ref = environment.getArgument("ref"); - GqlvMeta.Fetcher source = environment.getSource(); - environment.getGraphQlContext().put(keyFor(ref), new BookmarkedPojo(source.bookmark(), context.bookmarkService)); - return ref; - } - - public static String keyFor(String ref) { - return GqlvMetaSaveAs.class.getName() + "#" + ref; + return fetchDataFromBlob(environment, Blob::getName); } } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/TypeNames.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/TypeNames.java index ca90a3f5b2..abe4d837f0 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/TypeNames.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/TypeNames.java @@ -61,6 +61,10 @@ public final class TypeNames { return objectTypeNameFor(owningType) + "__" + oneToOneAssociation.getId() + "__gqlv_property"; } + public static String propertyBlobTypeNameFor(ObjectSpecification owningType, OneToOneAssociation oneToOneAssociation) { + return objectTypeNameFor(owningType) + "__" + oneToOneAssociation.getId() + "__gqlv_property_blob"; + } + public static String collectionTypeNameFor(ObjectSpecification owningType, OneToManyAssociation objectMember) { return objectTypeNameFor(owningType) + "__" + objectMember.getId() + "__gqlv_collection"; } diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices._.gql new file mode 100644 index 0000000000..3f8207e675 --- /dev/null +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices._.gql @@ -0,0 +1,34 @@ +{ + Scenario(name: "checks choice 'saveAs' reference numbering") { + Given { + university_dept_Departments { + findDepartmentByName { + invoke(name: "Classics") { + addStaffMembers { + params { + staffMembers { + choices { + _gqlv_meta { + id + saveAs(ref: "staff-member-choices") + } + name { + get + } + } + } + } + } + } + } + } + } + Then { + university_dept_StaffMember(object: {ref: "staff-member-choices-2"}) { + name { + get + } + } + } + } +} diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices.approved.json new file mode 100644 index 0000000000..5d9cf1af12 --- /dev/null +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_member_choices.approved.json @@ -0,0 +1,52 @@ +{ + "data" : { + "Scenario" : { + "Given" : { + "university_dept_Departments" : { + "findDepartmentByName" : { + "invoke" : { + "addStaffMembers" : { + "params" : { + "staffMembers" : { + "choices" : [ { + "_gqlv_meta" : { + "id" : "15", + "saveAs" : "staff-member-choices" + }, + "name" : { + "get" : "John Gartner" + } + }, { + "_gqlv_meta" : { + "id" : "16", + "saveAs" : "staff-member-choices" + }, + "name" : { + "get" : "Margaret Randall" + } + }, { + "_gqlv_meta" : { + "id" : "14", + "saveAs" : "staff-member-choices" + }, + "name" : { + "get" : "Mervin Hughes" + } + } ] + } + } + } + } + } + } + }, + "Then" : { + "university_dept_StaffMember" : { + "name" : { + "get" : "Margaret Randall" + } + } + } + } + } +} \ No newline at end of file diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_members._.choices.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_members.choices._.gql similarity index 100% rename from viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_members._.choices.gql rename to viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.find_department_and_add_staff_members.choices._.gql diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.java index 44778f12b7..5db64cac19 100644 --- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.java +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Department_IntegTest.java @@ -113,6 +113,15 @@ public class Department_IntegTest extends Abstract_IntegTest { Approvals.verify(submit(), jsonOptions()); } + @Test + @UseReporter(DiffReporter.class) + void find_department_and_add_staff_member_choices() throws Exception { + + // when, then + Approvals.verify(submit(), jsonOptions()); + + } + @Test @UseReporter(DiffReporter.class) void find_department_and_add_staff_members() throws Exception { diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members._.gql index f4b574714d..5d3c3da6c5 100644 --- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members._.gql +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members._.gql @@ -9,7 +9,11 @@ get } photo { - get + get { + name + mimeType + bytes + } } } } diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members.approved.json index a78da165d1..58c2e97788 100644 --- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members.approved.json +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.list_all_staff_members.approved.json @@ -10,7 +10,11 @@ "get" : "LECTURER" }, "photo" : { - "get" : "StaffMember-photo-Bar.pdf [application/pdf]: 47488 bytes" + "get" : { + "name" : "StaffMember-photo-Bar.pdf", + "mimeType" : "application/pdf", + "bytes" : "///graphql/object/university.dept.StaffMember:658/photo/blobBytes" + } } }, { "name" : { @@ -20,7 +24,11 @@ "get" : "LECTURER" }, "photo" : { - "get" : null + "get" : { + "name" : null, + "mimeType" : null, + "bytes" : "///graphql/object/university.dept.StaffMember:660/photo/blobBytes" + } } }, { "name" : { @@ -30,7 +38,11 @@ "get" : "LECTURER" }, "photo" : { - "get" : "StaffMember-photo-Foo.pdf [application/pdf]: 47185 bytes" + "get" : { + "name" : "StaffMember-photo-Foo.pdf", + "mimeType" : "application/pdf", + "bytes" : "///graphql/object/university.dept.StaffMember:657/photo/blobBytes" + } } }, { "name" : { @@ -40,7 +52,11 @@ "get" : "LECTURER" }, "photo" : { - "get" : null + "get" : { + "name" : null, + "mimeType" : null, + "bytes" : "///graphql/object/university.dept.StaffMember:661/photo/blobBytes" + } } }, { "name" : { @@ -50,7 +66,11 @@ "get" : "LECTURER" }, "photo" : { - "get" : "StaffMember-photo-Fizz.pdf [application/pdf]: 46833 bytes" + "get" : { + "name" : "StaffMember-photo-Fizz.pdf", + "mimeType" : "application/pdf", + "bytes" : "///graphql/object/university.dept.StaffMember:659/photo/blobBytes" + } } } ] } diff --git a/viewers/graphql/test/src/test/resources/schema.gql b/viewers/graphql/test/src/test/resources/schema.gql index 5a059d85a2..4aca251bf4 100644 --- a/viewers/graphql/test/src/test/resources/schema.gql +++ b/viewers/graphql/test/src/test/resources/schema.gql @@ -228,6 +228,7 @@ type causeway_applib_DomainObjectList__gqlv_meta { } type causeway_applib_DomainObjectList__objects__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -249,6 +250,7 @@ type causeway_applib_FacetGroupNode { } type causeway_applib_FacetGroupNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -289,6 +291,7 @@ type causeway_applib_ParameterNode { } type causeway_applib_ParameterNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -331,6 +334,7 @@ type causeway_applib_PropertyNode { } type causeway_applib_PropertyNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -416,6 +420,7 @@ type causeway_applib_TypeNode { } type causeway_applib_TypeNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -557,6 +562,7 @@ type causeway_applib_UserMemento__realName__gqlv_property { } type causeway_applib_UserMemento__roles__gqlv_collection { + datatype: String disabled: String get: [causeway_applib_RoleMemento] hidden: Boolean @@ -601,6 +607,7 @@ type causeway_applib_node_ActionNode__action__gqlv_property { } type causeway_applib_node_ActionNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -643,6 +650,7 @@ type causeway_applib_node_CollectionNode { } type causeway_applib_node_CollectionNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -693,6 +701,7 @@ type causeway_applib_node_FacetAttrNode { } type causeway_applib_node_FacetAttrNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -735,6 +744,7 @@ type causeway_applib_node_FacetNode { } type causeway_applib_node_FacetNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -830,6 +840,7 @@ type causeway_conf_ConfigurationViewmodel { } type causeway_conf_ConfigurationViewmodel__environment__gqlv_collection { + datatype: String disabled: String get: [causeway_conf_ConfigurationProperty] hidden: Boolean @@ -847,12 +858,14 @@ type causeway_conf_ConfigurationViewmodel__gqlv_meta { } type causeway_conf_ConfigurationViewmodel__primary__gqlv_collection { + datatype: String disabled: String get: [causeway_conf_ConfigurationProperty] hidden: Boolean } type causeway_conf_ConfigurationViewmodel__secondary__gqlv_collection { + datatype: String disabled: String get: [causeway_conf_ConfigurationProperty] hidden: Boolean @@ -922,6 +935,7 @@ type causeway_feat_ApplicationNamespace { } type causeway_feat_ApplicationNamespace__contents__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -1281,12 +1295,14 @@ type causeway_feat_ApplicationTypeProperty__typicalLength__gqlv_property { } type causeway_feat_ApplicationType__actions__gqlv_collection { + datatype: String disabled: String get: [causeway_feat_ApplicationTypeAction] hidden: Boolean } type causeway_feat_ApplicationType__collections__gqlv_collection { + datatype: String disabled: String get: [causeway_feat_ApplicationTypeCollection] hidden: Boolean @@ -1330,6 +1346,7 @@ type causeway_feat_ApplicationType__parent__gqlv_property { } type causeway_feat_ApplicationType__properties__gqlv_collection { + datatype: String disabled: String get: [causeway_feat_ApplicationTypeProperty] hidden: Boolean @@ -1656,6 +1673,7 @@ type org_apache_causeway_core_metamodel_inspect_model_MMNode { } type org_apache_causeway_core_metamodel_inspect_model_MMNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -1687,6 +1705,7 @@ type org_apache_causeway_core_metamodel_inspect_model_MemberNode { } type org_apache_causeway_core_metamodel_inspect_model_MemberNode__childNodes__gqlv_collection { + datatype: String disabled: String hidden: Boolean } @@ -2511,6 +2530,7 @@ type university_dept_Department__removeStaffMember__staffMember__gqlv_action_par "Staff member of a university department, responsible for delivering lectures, tutorials, exam invigilation and candidate interviews" type university_dept_Department__staffMembers__gqlv_collection { + datatype: String disabled: String get: [university_dept_StaffMember] hidden: Boolean @@ -2755,12 +2775,18 @@ type university_dept_StaffMember__name__gqlv_property { type university_dept_StaffMember__photo__gqlv_property { datatype: String disabled: String - get: String + get: university_dept_StaffMember__photo__gqlv_property_blob hidden: Boolean set(photo: String): university_dept_StaffMember validate(photo: String): String } +type university_dept_StaffMember__photo__gqlv_property_blob { + bytes: String + mimeType: String + name: String +} + type university_dept_Staff__createStaffMember__department__gqlv_action_parameter { choices(name: String): [university_dept_Department] datatype: String diff --git a/viewers/graphql/viewer/pom.xml b/viewers/graphql/viewer/pom.xml index f83bdf9a1c..50ffa9b7e9 100644 --- a/viewers/graphql/viewer/pom.xml +++ b/viewers/graphql/viewer/pom.xml @@ -47,6 +47,10 @@ </build> <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot</artifactId> + </dependency> <dependency> <groupId>org.apache.causeway.viewer</groupId> <artifactId>causeway-viewer-graphql-model</artifactId> diff --git a/viewers/graphql/viewer/src/main/java/module-info.java b/viewers/graphql/viewer/src/main/java/module-info.java index d1d8a7bb90..4a9a36c67a 100644 --- a/viewers/graphql/viewer/src/main/java/module-info.java +++ b/viewers/graphql/viewer/src/main/java/module-info.java @@ -1,6 +1,7 @@ module org.apache.causeway.incubator.viewer.graphql.viewer { exports org.apache.causeway.viewer.graphql.viewer; exports org.apache.causeway.viewer.graphql.viewer.integration; + exports org.apache.causeway.viewer.graphql.viewer.controller; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; @@ -25,4 +26,5 @@ module org.apache.causeway.incubator.viewer.graphql.viewer { requires spring.graphql; requires spring.tx; requires org.apache.causeway.incubator.viewer.graphql.applib; + requires spring.web; } \ No newline at end of file diff --git a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/BlobBytesController.java b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/BlobBytesController.java new file mode 100644 index 0000000000..fc5ae3e523 --- /dev/null +++ b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/BlobBytesController.java @@ -0,0 +1,80 @@ +package org.apache.causeway.viewer.graphql.viewer.controller; + +import javax.inject.Inject; + +import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; + +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import org.apache.causeway.applib.services.bookmark.Bookmark; +import org.apache.causeway.applib.services.bookmark.BookmarkService; +import org.apache.causeway.applib.value.Blob; +import org.apache.causeway.core.metamodel.object.ManagedObject; +import org.apache.causeway.core.metamodel.objectmanager.ObjectManager; + +import lombok.RequiredArgsConstructor; +import lombok.Value; + +import java.util.Optional; + +@RestController(value = "/graphql/object") +@RequiredArgsConstructor(onConstructor_ = {@Inject}) +public class BlobBytesController { + + private final BookmarkService bookmarkService; + private final ObjectManager objectManager; + + @Value(staticConstructor = "of") + private static class ManagedObjectAndPropertyIfAny { + ManagedObject owningObject; + Optional<OneToOneAssociation> propertyIfAny; + boolean isPropertyPresent() { + return propertyIfAny.isPresent(); + } + } + + private static class ManagedObjectAndProperty { + private static ManagedObjectAndProperty of(ManagedObjectAndPropertyIfAny tuple) { + return new ManagedObjectAndProperty(tuple); + } + private ManagedObjectAndProperty(ManagedObjectAndPropertyIfAny tuple) { + this.owningObject = tuple.owningObject; + this.property = tuple.propertyIfAny.orElse(null); + } + ManagedObject owningObject; + OneToOneAssociation property; + + public ManagedObject value() { + return property.get(owningObject); + } + } + + @GetMapping(value = "/{logicalTypeName}:{id}/{propertyId}/blobBytes") + public ResponseEntity<byte[]> propertyBlobBytes( + @PathVariable final String logicalTypeName, + @PathVariable final String id, + @PathVariable final String propertyId) { + + return bookmarkService.lookup(Bookmark.forLogicalTypeNameAndIdentifier(logicalTypeName, id)) + .map(objectManager::adapt) + .map(managedObject -> ManagedObjectAndPropertyIfAny.of(managedObject, managedObject.getSpecification().getProperty(propertyId))) + .filter(ManagedObjectAndPropertyIfAny::isPropertyPresent) + .map(ManagedObjectAndProperty::of) + .map(ManagedObjectAndProperty::value) + .map(ManagedObject::getPojo) + .filter(Blob.class::isInstance) + .map(Blob.class::cast) + .map(blob -> ResponseEntity.ok() + .contentType(MediaType.APPLICATION_PDF) + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(blob.getName()).build().toString()) + .contentLength(blob.getBytes().length) + .body(blob.getBytes())) + .orElse(ResponseEntity.notFound().build()); + } +}
