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 bd77ee5bb7bca34b13f57dec4bf2065c209862d2 Author: danhaywood <[email protected]> AuthorDate: Thu Feb 15 07:12:03 2024 +0000 CAUSEWAY-3676: use addChildFieldFor where possible; removes 'grid' from test; adds clob controller --- .../graphql/model/domain/GqlvAbstractCustom.java | 14 ++-- .../viewer/graphql/model/domain/GqlvAction.java | 33 ++++---- .../graphql/model/domain/GqlvActionParam.java | 41 ++-------- .../graphql/model/domain/GqlvActionParams.java | 4 +- .../graphql/model/domain/GqlvCollection.java | 15 +--- .../graphql/model/domain/GqlvDomainObject.java | 25 +++--- .../graphql/model/domain/GqlvDomainService.java | 2 +- .../viewer/graphql/model/domain/GqlvMeta.java | 45 ++++------- .../viewer/graphql/model/domain/GqlvProperty.java | 71 ++++++++--------- .../graphql/model/domain/GqlvPropertyGetClob.java | 90 ++++++++++++++++++++++ .../model/domain/GqlvPropertyGetClobAbstract.java | 78 +++++++++++++++++++ .../model/domain/GqlvPropertyGetClobChars.java | 49 ++++++++++++ .../model/domain/GqlvPropertyGetClobMimeType.java | 39 ++++++++++ .../model/domain/GqlvPropertyGetClobName.java | 39 ++++++++++ .../viewer/graphql/model/domain/GqlvScenario.java | 3 +- .../graphql/model/domain/GqlvScenarioStep.java | 10 ++- .../model/toplevel/GqlvTopLevelMutation.java | 4 +- .../graphql/model/toplevel/GqlvTopLevelQuery.java | 13 ++-- ...gTest.create_staff_member_with_department._.gql | 1 - ...eate_staff_member_with_department.approved.json | 3 +- .../viewer/CausewayModuleViewerGraphqlViewer.java | 4 +- ...ytesController.java => ResourceController.java} | 41 +++++++--- 22 files changed, 447 insertions(+), 177 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 479b7cc62e..5d4e78a012 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 @@ -27,6 +27,8 @@ import static graphql.schema.GraphQLObjectType.newObject; import org.apache.causeway.viewer.graphql.model.context.Context; +import org.springframework.lang.Nullable; + import lombok.AccessLevel; import lombok.Getter; @@ -59,15 +61,17 @@ 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) { + protected final void addChildFieldFor(@Nullable GqlvAbstract hasField) { if (isBuilt()) { + throw new IllegalStateException("Object type has already been built"); + } + if (hasField == null) { return; } + addChildField(hasField.getField()); + } + void addChildField(GraphQLFieldDefinition childField) { if (childField != null) { gqlObjectTypeBuilder.field(childField); } 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 fa8ef68e2c..7c46953a23 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 @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.commons.collections.Can; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; @@ -76,23 +77,29 @@ public class GqlvAction 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) { - addChildFieldFor(this.invoke = new GqlvActionInvoke(this, context)); - } else { - this.invoke = null; - } - val params = new GqlvActionParams(this, context); - if (params.hasParams()) { - this.params = params; - addChildField(params.getField()); - } else { - this.params = null; - } + addChildFieldFor( + this.invoke = isInvokeAllowed(objectAction) + ? new GqlvActionInvoke(this, context) + : null); + addChildFieldFor(this.params = new GqlvActionParams(this, context)); buildObjectTypeAndField(objectAction.getId()); } + private boolean isInvokeAllowed(ObjectAction objectAction) { + val apiVariant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); + switch (apiVariant) { + case QUERY_ONLY: + case QUERY_AND_MUTATIONS: + return objectAction.getSemantics().isSafeInNature(); + case QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT: + return true; + default: + // shouldn't happen + throw new IllegalArgumentException("Unknown API variant: " + apiVariant); + } + } + public Can<ManagedObject> argumentManagedObjectsFor( final DataFetchingEnvironment dataFetchingEnvironment, final ObjectAction objectAction, diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParam.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParam.java index d9f6d096dc..6cf6e6df7a 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParam.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParam.java @@ -79,40 +79,13 @@ public class GqlvActionParam this.objectActionParameter = objectActionParameter; this.paramNum = paramNum; - this.hidden = new GqlvActionParamHidden(this, context); - addChildField(hidden.getField()); - this.disabled = new GqlvActionParamDisabled(this, context); - addChildField(disabled.getField()); - - val choices = new GqlvActionParamChoices(this, context); - addChildField(choices.getField()); - if (choices.isFieldDefined()) { - this.choices = choices; - } else { - this.choices = null; - } - - val autoComplete = new GqlvActionParamAutoComplete(this, context); - addChildField(autoComplete.getField()); - if (autoComplete.isFieldDefined()) { - this.autoComplete = autoComplete; - } else { - this.autoComplete = null; - } - - val default_ = new GqlvActionParamDefault(this, context); - addChildField(default_.getField()); - if (default_.isFieldDefined()) { - this.default_ = default_; - } else { - this.default_ = null; - } - - this.validate = new GqlvActionParamValidate(this, context); - addChildField(validate.getField()); - - this.datatype = new GqlvActionParamDatatype(this, context); - addChildField(datatype.getField()); + addChildFieldFor(this.hidden = new GqlvActionParamHidden(this, context)); + addChildFieldFor(this.disabled = new GqlvActionParamDisabled(this, context)); + addChildFieldFor(this.choices = new GqlvActionParamChoices(this, context)); + addChildFieldFor(this.autoComplete = new GqlvActionParamAutoComplete(this, context)); + addChildFieldFor(this.default_ = new GqlvActionParamDefault(this, context)); + addChildFieldFor(this.validate = new GqlvActionParamValidate(this, context)); + addChildFieldFor(this.datatype = new GqlvActionParamDatatype(this, context)); buildObjectTypeAndField(objectActionParameter.getId()); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParams.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParams.java index e043d61d1d..0eccf8d435 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParams.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionParams.java @@ -85,8 +85,8 @@ public class GqlvActionParams } void addParam(ObjectActionParameter objectActionParameter, int paramNum) { - GqlvActionParam gqlvActionParam = new GqlvActionParam(this, objectActionParameter, context, paramNum); - addChildField(gqlvActionParam.getField()); + val gqlvActionParam = new GqlvActionParam(this, objectActionParameter, context, paramNum); + addChildFieldFor(gqlvActionParam); params.put(objectActionParameter.getId(), gqlvActionParam); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvCollection.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvCollection.java index 4d3ab7a15c..c5b4ff6343 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvCollection.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvCollection.java @@ -41,17 +41,10 @@ public class GqlvCollection ) { super(holder, oneToManyAssociation, TypeNames.collectionTypeNameFor(holder.getObjectSpecification(), oneToManyAssociation), context); - this.hidden = new GqlvMemberHidden<>(this, context); - addChildField(hidden.getField()); - - this.disabled = new GqlvMemberDisabled<>(this, context); - addChildField(disabled.getField()); - - this.get = new GqlvCollectionGet(this, context); - addChildField(get.getField()); - - this.datatype = new GqlvCollectionDatatype(this, context); - addChildField(datatype.getField()); + addChildFieldFor(this.hidden = new GqlvMemberHidden<>(this, context)); + addChildFieldFor(this.disabled = new GqlvMemberDisabled<>(this, context)); + addChildFieldFor(this.get = new GqlvCollectionGet(this, context)); + addChildFieldFor(this.datatype = new GqlvCollectionDatatype(this, context)); buildObjectTypeAndField(oneToManyAssociation.getId()); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java index e4f895fd26..71a88f83e6 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java @@ -74,11 +74,10 @@ public class GqlvDomainObject this.objectSpecification = objectSpecification; gqlObjectTypeBuilder.description(objectSpecification.getDescription()); - this.meta = new GqlvMeta(this, context); - addChildField(meta.getField()); + addChildFieldFor(this.meta = new GqlvMeta(this, context)); - GraphQLInputObjectType.Builder inputTypeBuilder = newInputObject().name(TypeNames.inputTypeNameFor(objectSpecification)); - inputTypeBuilder + val inputObjectTypeBuilder = newInputObject().name(TypeNames.inputTypeNameFor(objectSpecification)); + inputObjectTypeBuilder .field(newInputObjectField() .name("id") .type(Scalars.GraphQLID) @@ -90,7 +89,7 @@ public class GqlvDomainObject .build() ) ; - gqlInputObjectType = inputTypeBuilder.build(); + gqlInputObjectType = inputObjectTypeBuilder.build(); setField(buildFieldDefinition(gqlInputObjectType)); @@ -129,8 +128,8 @@ public class GqlvDomainObject objectSpecification.streamActions(context.getActionScope(), MixedIn.INCLUDED) .forEach(objectAction -> { - GqlvAction gqlvAction = new GqlvAction(this, objectAction, context); - addChildField(gqlvAction.getField()); + val gqlvAction = new GqlvAction(this, objectAction, context); + addChildFieldFor(gqlvAction); actions.put(objectAction.getId(), gqlvAction); }); } @@ -143,16 +142,16 @@ public class GqlvDomainObject } private void addProperty(final OneToOneAssociation otoa) { - GqlvProperty gqlvProperty = new GqlvProperty(this, otoa, context); - addChildField(gqlvProperty.getField()); + val gqlvProperty = new GqlvProperty(this, otoa, context); + addChildFieldFor(gqlvProperty); properties.put(otoa.getId(), gqlvProperty); } private void addCollection(OneToManyAssociation otom) { - GqlvCollection collection = new GqlvCollection(this, otom, context); - addChildField(collection.getField()); - if (collection.isFieldDefined()) { - collections.put(otom.getId(), collection); + val gqlvCollection = new GqlvCollection(this, otom, context); + addChildFieldFor(gqlvCollection); + if (gqlvCollection.isFieldDefined()) { + collections.put(otom.getId(), gqlvCollection); } } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java index 8ffe5a6c85..7a51948524 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainService.java @@ -82,7 +82,7 @@ public class GqlvDomainService private void addAction(final ObjectAction objectAction) { val gqlvAction = new GqlvAction(this, objectAction, context); - addChildField(gqlvAction.getField()); + addChildFieldFor(gqlvAction); actions.put(objectAction.getId(), gqlvAction); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java index 15181e1c19..bd12ca4380 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java @@ -57,46 +57,29 @@ public class GqlvMeta extends GqlvAbstractCustom { super(TypeNames.metaTypeNameFor(holder.getObjectSpecification()), context); this.holder = holder; - metaId = new GqlvMetaId(context); - addChildField(metaId.getField()); - - metaLogicalTypeName = new GqlvMetaLogicalTypeName(context); - addChildField(metaLogicalTypeName.getField()); - - if (holder.getObjectSpecification().getBeanSort() == BeanSort.ENTITY) { - metaVersion = new GqlvMetaVersion(context); - addChildField(metaVersion.getField()); - } else { - metaVersion = null; - } - - metaTitle = new GqlvMetaTitle(context); - addChildField(metaTitle.getField()); - - metaIconName = new GqlvMetaIconName(context); - addChildField(metaIconName.getField()); - - metaCssClass = new GqlvMetaCssClass(context); - addChildField(metaCssClass.getField()); - - metaLayout = new GqlvMetaLayout(context); - addChildField(metaLayout.getField()); - - metaGrid = new GqlvMetaGrid(context); - addChildField(metaGrid.getField()); - - metaSaveAs = new GqlvMetaSaveAs(context); - addChildField(metaSaveAs.getField()); + addChildFieldFor(this.metaId = new GqlvMetaId(context)); + addChildFieldFor(this.metaLogicalTypeName = new GqlvMetaLogicalTypeName(context)); + addChildFieldFor(this.metaVersion = isEntity() ? new GqlvMetaVersion(context) : null); + addChildFieldFor(this.metaTitle = new GqlvMetaTitle(context)); + addChildFieldFor(this.metaIconName = new GqlvMetaIconName(context)); + addChildFieldFor(this.metaCssClass = new GqlvMetaCssClass(context)); + addChildFieldFor(this.metaLayout = new GqlvMetaLayout(context)); + addChildFieldFor(this.metaGrid = new GqlvMetaGrid(context)); + addChildFieldFor(this.metaSaveAs = new GqlvMetaSaveAs(context)); val fieldName = context.causewayConfiguration.getViewer().getGraphql().getMetaData().getFieldName(); buildObjectTypeAndField(fieldName); } + private boolean isEntity() { + return holder.getObjectSpecification().getBeanSort() == BeanSort.ENTITY; + } + @Override protected void addDataFetchersForChildren() { metaId.addDataFetcher(this); metaLogicalTypeName.addDataFetcher(this); - if (holder.getObjectSpecification().getBeanSort() == BeanSort.ENTITY) { + if (isEntity()) { metaVersion.addDataFetcher(this); } metaTitle.addDataFetcher(this); 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 7e82bd35fc..f6ffed52d3 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 @@ -25,6 +25,7 @@ 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.ObjectAction; 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.types.TypeMapper; @@ -41,7 +42,8 @@ public class GqlvProperty GqlvPropertyValidate.Holder, GqlvPropertySet.Holder, GqlvAssociationDatatype.Holder<OneToOneAssociation>, - GqlvPropertyGetBlob.Holder { + GqlvPropertyGetBlob.Holder, + GqlvPropertyGetClob.Holder { private final GqlvMemberHidden<OneToOneAssociation> hidden; private final GqlvMemberDisabled<OneToOneAssociation> disabled; @@ -68,51 +70,42 @@ public class GqlvProperty final Context context) { super(holder, oneToOneAssociation, TypeNames.propertyTypeNameFor(holder.getObjectSpecification(), oneToOneAssociation), context); - this.hidden = new GqlvMemberHidden<>(this, context); - addChildField(hidden.getField()); + addChildFieldFor(this.hidden = new GqlvMemberHidden<>(this, context)); + addChildFieldFor(this.disabled = new GqlvMemberDisabled<>(this, context)); - this.disabled = new GqlvMemberDisabled<>(this, context); - addChildField(disabled.getField()); + this.get = isBlob() ? new GqlvPropertyGetBlob(this, context) : isClob() ? new GqlvPropertyGetClob(this, context) : new GqlvPropertyGet(this, context); + addChildFieldFor( + isBlob() + ? new GqlvPropertyGetBlob(this, context) + : isClob() + ? new GqlvPropertyGetClob(this, context) + : 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); - addChildField(this.validate.getField()); + addChildFieldFor(this.validate = new GqlvPropertyValidate(this, context)); + addChildFieldFor(this.choices = new GqlvPropertyChoices(this, context)); + addChildFieldFor(this.autoComplete = new GqlvPropertyAutoComplete(this, context)); + addChildFieldFor(this.set = isSetterAllowed() ? new GqlvPropertySet(this, context) : null); + addChildFieldFor(this.datatype = new GqlvPropertyDatatype(this, context)); - val choices = new GqlvPropertyChoices(this, context); - if (choices.isFieldDefined()) { - addChildField(choices.getField()); - this.choices = choices; - } else { - this.choices = null; - } - - val autoComplete = new GqlvPropertyAutoComplete(this, context); - if (autoComplete.isFieldDefined()) { - addChildField(autoComplete.getField()); - this.autoComplete = autoComplete; - } else { - this.autoComplete = null; - } + buildObjectTypeAndField(oneToOneAssociation.getId()); + } - val variant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); - if (variant == CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT) { - this.set = new GqlvPropertySet(this, context); - addChildField(set.getField()); - } else { - this.set = null; + private boolean isSetterAllowed() { + val apiVariant = context.causewayConfiguration.getViewer().getGraphql().getApiVariant(); + switch (apiVariant) { + case QUERY_ONLY: + case QUERY_AND_MUTATIONS: + return false; + case QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT: + return true; + default: + // shouldn't happen + throw new IllegalArgumentException("Unknown API variant: " + apiVariant); } + } - this.datatype = new GqlvPropertyDatatype(this, context); - addChildField(datatype.getField()); - buildObjectTypeAndField(oneToOneAssociation.getId()); - } private boolean isBlob() { return getOneToOneAssociation().getElementType().getCorrespondingClass() == Blob.class; diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java new file mode 100644 index 0000000000..1944815515 --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.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 GqlvPropertyGetClob + extends GqlvAbstractCustom + implements GqlvPropertyGetClobChars.Holder +{ + + final Holder holder; + final GqlvPropertyGetClobChars clobChars; + final GqlvPropertyGetClobMimeType clobMimeType; + final GqlvPropertyGetClobName clobName; + + public GqlvPropertyGetClob( + final Holder holder, + final Context context) { + super(TypeNames.propertyBlobTypeNameFor(holder.getObjectSpecification(), holder.getObjectMember()), context); + this.holder = holder; + + addChildFieldFor(clobChars = new GqlvPropertyGetClobChars(this, context)); + addChildFieldFor(clobMimeType = new GqlvPropertyGetClobMimeType(this, context)); + addChildFieldFor(clobName = new GqlvPropertyGetClobName(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() { + clobChars.addDataFetcher(this); + clobMimeType.addDataFetcher(this); + clobName.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/GqlvPropertyGetClobAbstract.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobAbstract.java new file mode 100644 index 0000000000..87107f919c --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobAbstract.java @@ -0,0 +1,78 @@ +/* + * 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 java.util.Optional; +import java.util.function.Function; + +import graphql.Scalars; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLFieldDefinition; + +import org.apache.causeway.applib.value.Blob; +import org.apache.causeway.core.metamodel.object.ManagedObject; +import org.apache.causeway.viewer.graphql.model.context.Context; +import org.apache.causeway.viewer.graphql.model.domain.GqlvAbstract; +import org.apache.causeway.viewer.graphql.model.domain.GqlvPropertyGet; +import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo; + +import lombok.val; + +public abstract class GqlvPropertyGetClobAbstract extends GqlvAbstract { + + final Holder holder; + + public GqlvPropertyGetClobAbstract( + final Holder holder, + final Context context, String name) { + super(context); + this.holder = holder; + + setField(GraphQLFieldDefinition.newFieldDefinition() + .name(name) + .type(Scalars.GraphQLString) + .build()); + } + + protected Object fetchDataFromBlob(DataFetchingEnvironment environment, Function<Blob, ?> mapper) { + val sourcePojo = BookmarkedPojo.sourceFrom(environment); + + val sourcePojoClass = sourcePojo.getClass(); + val objectSpecification = context.specificationLoader.loadSpecification(sourcePojoClass); + if (objectSpecification == null) { + // not expected + return null; + } + + val association = holder.getObjectAssociation(); + val managedObject = ManagedObject.adaptSingular(objectSpecification, sourcePojo); + val resultManagedObject = association.get(managedObject); + + return Optional.ofNullable(resultManagedObject) + .map(ManagedObject::getPojo) + .filter(Blob.class::isInstance) + .map(Blob.class::cast) + .map(mapper) + .orElse(null); + } + + public interface Holder extends GqlvPropertyGet.Holder { + } + +} diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java new file mode 100644 index 0000000000..fd59b02d32 --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java @@ -0,0 +1,49 @@ +/* + * 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 java.util.Optional; + +import graphql.schema.DataFetchingEnvironment; + +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; + +import lombok.val; + +public class GqlvPropertyGetClobChars extends GqlvPropertyGetClobAbstract { + + public GqlvPropertyGetClobChars( + final Holder holder, + final Context context) { + super(holder, context, "bytes"); + } + + @Override + protected Object fetchData(DataFetchingEnvironment environment) { + val sourcePojo = BookmarkedPojo.sourceFrom(environment); + + Optional<Bookmark> bookmarkIfAny = context.bookmarkService.bookmarkFor(sourcePojo); + return bookmarkIfAny.map(x -> String.format( + "///%s/object/%s:%s/%s/clobChars", "graphql", x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); + + } + +} diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobMimeType.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobMimeType.java new file mode 100644 index 0000000000..9f314203f3 --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobMimeType.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.causeway.viewer.graphql.model.context.Context; + +public class GqlvPropertyGetClobMimeType extends GqlvPropertyGetClobAbstract { + + public GqlvPropertyGetClobMimeType( + final Holder holder, + final Context context) { + super(holder, context, "mimeType"); + + } + + @Override + protected Object fetchData(DataFetchingEnvironment environment) { + return fetchDataFromBlob(environment, blob -> blob.getMimeType().toString()); + } + +} diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobName.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobName.java new file mode 100644 index 0000000000..5ea781c578 --- /dev/null +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobName.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.causeway.applib.value.Blob; +import org.apache.causeway.viewer.graphql.model.context.Context; + +public class GqlvPropertyGetClobName extends GqlvPropertyGetClobAbstract { + + public GqlvPropertyGetClobName( + final Holder holder, + final Context context) { + super(holder, context, "name"); + } + + @Override + protected Object fetchData(DataFetchingEnvironment environment) { + return fetchDataFromBlob(environment, Blob::getName); + } + +} diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenario.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenario.java index ede625c72e..07e4f851bf 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenario.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenario.java @@ -41,8 +41,7 @@ public class GqlvScenario final Context context) { super("Scenario", context); - this.scenarioName = new GqlvScenarioName(context); - addChildField(scenarioName.getField()); + addChildFieldFor(this.scenarioName = new GqlvScenarioName(context)); this.scenarioStep = new GqlvScenarioStep(context); addChildField(scenarioStep.newField("Given")); diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java index a852daaa71..4411e63aa5 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java @@ -6,6 +6,8 @@ import java.util.Objects; import graphql.schema.DataFetchingEnvironment; +import lombok.val; + import org.apache.causeway.applib.services.metamodel.BeanSort; import org.apache.causeway.viewer.graphql.model.context.Context; @@ -36,16 +38,16 @@ public class GqlvScenarioStep if (Objects.requireNonNull(objectSpec.getBeanSort()) == BeanSort.MANAGED_BEAN_CONTRIBUTING) { // @DomainService context.serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName()) .ifPresent(servicePojo -> { - GqlvDomainService gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context); - addChildField(gqlvDomainService.getField()); + val gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context); + addChildFieldFor(gqlvDomainService); domainServices.add(gqlvDomainService); }); } }); // add domain object lookup to top-level query - for (GqlvDomainObject domainObject : this.domainObjects) { - addChildField(domainObject.getField()); + for (val gqlvDomainObject : this.domainObjects) { + addChildFieldFor(gqlvDomainObject); } buildObjectType(); diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelMutation.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelMutation.java index 3fa541054f..20a692f50f 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelMutation.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelMutation.java @@ -57,13 +57,13 @@ public class GqlvTopLevelMutation public void addAction(ObjectSpecification objectSpec, final ObjectAction objectAction) { val gqlvMutationForAction = new GqlvMutationForAction(objectSpec, objectAction, context); - addChildField(gqlvMutationForAction.getField()); + addChildFieldFor(gqlvMutationForAction); actions.add(gqlvMutationForAction); } public void addProperty(ObjectSpecification objectSpec, final OneToOneAssociation property) { val gqlvMutationForProperty = new GqlvMutationForProperty(objectSpec, property, context); - addChildField(gqlvMutationForProperty.getField()); + addChildFieldFor(gqlvMutationForProperty); properties.add(gqlvMutationForProperty); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java index 1668709db0..b0baf4d332 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java @@ -6,6 +6,8 @@ import java.util.List; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLObjectType; +import lombok.val; + import org.apache.causeway.viewer.graphql.model.context.Context; import org.apache.causeway.viewer.graphql.model.domain.GqlvAbstractCustom; import org.apache.causeway.viewer.graphql.model.domain.GqlvDomainObject; @@ -44,8 +46,8 @@ public class GqlvTopLevelQuery case MANAGED_BEAN_CONTRIBUTING: // @DomainService context.serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName()) .ifPresent(servicePojo -> { - GqlvDomainService gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context); - addChildField(gqlvDomainService.getField()); + val gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context); + addChildFieldFor(gqlvDomainService); domainServices.add(gqlvDomainService); }); break; @@ -53,12 +55,11 @@ public class GqlvTopLevelQuery }); // add domain object lookup to top-level query - for (GqlvDomainObject domainObject : this.domainObjects) { - addChildField(domainObject.getField()); + for (val gqlvDomainObject : this.domainObjects) { + addChildFieldFor(gqlvDomainObject); } - scenario = new GqlvScenario(context); - addChildField(scenario.getField()); + addChildFieldFor(scenario = new GqlvScenario(context)); buildObjectType(); } diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql index 5876019ffb..b18a3e5a64 100644 --- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department._.gql @@ -32,7 +32,6 @@ version cssClass iconName - grid } } } diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json index 1929ae96ef..4548f25fb4 100644 --- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json +++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/queryandmutations/Staff_IntegTest.create_staff_member_with_department.approved.json @@ -32,8 +32,7 @@ "logicalTypeName" : "university.dept.StaffMember", "version" : null, "cssClass" : null, - "iconName" : null, - "grid" : "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bs:grid xmlns:bs=\"https://causeway.apache.org/applib/layout/grid/bootstrap3\" xmlns:lnk=\"https://causeway.apache.org/applib/layout/links\" xmlns:cpt=\"https://causeway.apache.org/applib/layout/component\">\n <bs:row>\n <bs:col span=\"12\" unreferencedActions=\"true\">\n <cpt:domainObject bookmarking=\"AS_ROOT\"/>\n </bs:col>\n </bs:row>\n <bs:row>\n <bs:col span=\"4\">\n [...] + "iconName" : null } } } diff --git a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/CausewayModuleViewerGraphqlViewer.java b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/CausewayModuleViewerGraphqlViewer.java index 1c28790e91..1f52b24e61 100644 --- a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/CausewayModuleViewerGraphqlViewer.java +++ b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/CausewayModuleViewerGraphqlViewer.java @@ -18,7 +18,7 @@ */ package org.apache.causeway.viewer.graphql.viewer; -import org.apache.causeway.viewer.graphql.viewer.controller.BlobBytesController; +import org.apache.causeway.viewer.graphql.viewer.controller.ResourceController; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration; @@ -47,7 +47,7 @@ import org.apache.causeway.viewer.graphql.model.CausewayModuleViewerGraphqlModel GraphQlWebMvcAutoConfiguration.class, // controllers - BlobBytesController.class + ResourceController.class }) @EnableConfigurationProperties({ GraphQlProperties.class, GraphQlCorsProperties.class 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/ResourceController.java similarity index 71% rename from viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/BlobBytesController.java rename to viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java index a72670ce79..2b388ce3cf 100644 --- 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/ResourceController.java @@ -2,12 +2,14 @@ package org.apache.causeway.viewer.graphql.viewer.controller; import javax.inject.Inject; +import org.apache.causeway.applib.value.Clob; 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.util.MimeType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,7 +29,7 @@ import java.util.Optional; @RestController() @RequestMapping("/graphql/object") @RequiredArgsConstructor(onConstructor_ = {@Inject}) -public class BlobBytesController { +public class ResourceController { private final BookmarkService bookmarkService; private final ObjectManager objectManager; @@ -38,23 +40,44 @@ public class BlobBytesController { @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) + return valueOf(logicalTypeName, id, propertyId) .filter(Blob.class::isInstance) .map(Blob.class::cast) .map(blob -> ResponseEntity.ok() - .contentType(MediaType.APPLICATION_PDF) + .contentType(MediaType.asMediaType(MimeType.valueOf(blob.getMimeType().toString()))) .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(blob.getName()).build().toString()) .contentLength(blob.getBytes().length) .body(blob.getBytes())) .orElse(ResponseEntity.notFound().build()); } + @GetMapping(value = "/{logicalTypeName}:{id}/{propertyId}/clobChars") + public ResponseEntity<CharSequence> propertyClobChars( + @PathVariable final String logicalTypeName, + @PathVariable final String id, + @PathVariable final String propertyId) { + + return valueOf(logicalTypeName, id, propertyId) + .filter(Clob.class::isInstance) + .map(Clob.class::cast) + .map(clob -> ResponseEntity.ok() + .contentType(MediaType.asMediaType(MimeType.valueOf(clob.getMimeType().toString()))) + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(clob.getName()).build().toString()) + .contentLength(clob.getChars().length()) + .body(clob.getChars())) + .orElse(ResponseEntity.notFound().build()); + } + + private Optional<Object> valueOf(String logicalTypeName, String id, 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); + } + @Value(staticConstructor = "of") private static class ManagedObjectAndPropertyIfAny { ManagedObject owningObject;
