You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2024/02/15 00:01:41 UTC
(causeway) 01/02: CAUSEWAY-3676: adds support for retrieving blob bytes via rest controller;
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 e5cffada53d1dddc2a7647cd0ad2594f3f8feeec
Author: danhaywood <da...@haywood-associates.co.uk>
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());
+ }
+}