You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/04/07 14:44:59 UTC

[isis] branch master updated: ISIS-2994: make ValueFacet a first class citizen in ObjectSpecification

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new da29590512 ISIS-2994: make ValueFacet a first class citizen in ObjectSpecification
da29590512 is described below

commit da295905128702d2e55d193e0e742018be3636ac
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Apr 7 16:44:51 2022 +0200

    ISIS-2994: make ValueFacet a first class citizen in ObjectSpecification
    
    - provides consistent ObjectSpecification.isValue()
---
 .../isis/core/metamodel/facetapi/FacetHolder.java  | 24 ----------------
 ...ionOrAnyMatchingValueSemanticsFacetFactory.java | 21 ++++++++++++--
 .../interactions/managed/ActionInteraction.java    |  4 +--
 .../interactions/managed/_BindingUtil.java         |  9 +++---
 .../identify/ObjectBookmarker_builtinHandlers.java |  5 ++--
 .../isis/core/metamodel/spec/ManagedObject.java    |  3 +-
 .../core/metamodel/spec/ObjectSpecification.java   | 26 ++++--------------
 .../metamodel/spec/feature/ObjectAssociation.java  |  5 ++--
 .../specimpl/dflt/ObjectSpecificationDefault.java  | 29 +++++++++++++++++---
 .../apache/isis/core/metamodel/util/Facets.java    | 32 ++++++++++++----------
 .../core/metamodel/util/snapshot/XmlSnapshot.java  |  5 ++--
 .../testspec/ObjectSpecificationStub.java          |  6 ++++
 .../domainobjects/ObjectAndActionInvocation.java   |  2 +-
 .../domainobjects/ObjectPropertyReprRenderer.java  |  2 +-
 .../domainobjects/ScalarValueReprRenderer.java     |  3 +-
 .../JsonValueEncoderTest_asAdapter.java            |  8 ++++++
 .../converter/ConverterBasedOnValueSemantics.java  |  4 +--
 .../entity/EntityComponentFactoryAbstract.java     | 10 ++-----
 .../scalars/image/JavaAwtImagePanelFactory.java    | 14 ++++++----
 .../components/scalars/image/WicketImageUtil.java  |  5 ++--
 .../value/fallback/ValueFallbackPanelFactory.java  |  7 +++--
 21 files changed, 116 insertions(+), 108 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetHolder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetHolder.java
index b0c24f3c96..fdf625b0ef 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetHolder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/FacetHolder.java
@@ -23,10 +23,7 @@ import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
-import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.context.HasMetaModelContext;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 
 import lombok.NonNull;
 import lombok.val;
@@ -68,27 +65,6 @@ public interface FacetHolder extends HasMetaModelContext {
         return lookupFacet(facetType, facet->!facet.getPrecedence().isFallback());
     }
 
-    // -- VALUE SEMANTICS LOOKUP
-
-    //TODO support qualified selection honoring (@Qualifier) ... all the logic is in ValueFacet
-    default boolean hasValueSemantics(
-            final @NonNull Class<?> requiredType) {
-        return lookupFacet(ValueFacet.class).stream()
-        .map(ValueFacet::getAllValueSemantics)
-        .flatMap(Can<ValueSemanticsProvider<?>>::stream)
-        .anyMatch(valueSemantics->requiredType.isAssignableFrom(valueSemantics.getClass()));
-    }
-
-    //TODO support qualified selection honoring (@Qualifier) ... all the logic is in ValueFacet
-    default <T> Stream<T> streamValueSemantics(
-            final @NonNull Class<T> requiredType) {
-        return lookupFacet(ValueFacet.class).stream()
-        .map(ValueFacet::getAllValueSemantics)
-        .flatMap(Can<ValueSemanticsProvider<?>>::stream)
-        .filter(valueSemantics->requiredType.isAssignableFrom(valueSemantics.getClass()))
-        .map(requiredType::cast);
-    }
-
     // -- CONTAINS
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/annotcfg/ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/annotcfg/ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory.java
index 8bd3d4fe27..e54a230f80 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/annotcfg/ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/annotcfg/ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory.java
@@ -25,6 +25,7 @@ import org.apache.isis.applib.annotation.Value;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
 import org.apache.isis.applib.value.semantics.ValueSemanticsResolver;
+import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
@@ -39,8 +40,10 @@ import org.apache.isis.core.metamodel.facets.object.title.parser.TitleFacetFromV
 import org.apache.isis.core.metamodel.facets.object.value.ImmutableFacetViaValueSemantics;
 import org.apache.isis.core.metamodel.facets.object.value.MaxLengthFacetFromValueFacet;
 import org.apache.isis.core.metamodel.facets.object.value.TypicalLengthFacetFromValueFacet;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.facets.object.value.vsp.ValueFacetUsingSemanticsProvider;
 import org.apache.isis.core.metamodel.facets.value.annotation.LogicalTypeFacetForValueAnnotation;
+import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
 
 import lombok.AccessLevel;
 import lombok.Getter;
@@ -94,12 +97,14 @@ extends FacetFactoryAbstract {
 
         val identifier = Identifier.classIdentifier(logicalType);
 
-        addAllFacetsForValueSemantics(identifier, valueClass, facetHolder, valueIfAny);
+        val valueFacetIfAny = addAllFacetsForValueSemantics(identifier, valueClass, facetHolder, valueIfAny);
+
+        emitValueFacetProcessed(facetHolder, _Casts.uncheckedCast(valueFacetIfAny));
     }
 
     // -- HELPER
 
-    private <T> void addAllFacetsForValueSemantics(
+    private <T> Optional<ValueFacet<T>> addAllFacetsForValueSemantics(
             final Identifier identifier,
             final Class<T> valueClass,
             final FacetHolder holder,
@@ -112,7 +117,8 @@ extends FacetFactoryAbstract {
                         + "the type was found to be annotated with @Value", valueClass);
                 // fall through, so gets a ValueFacet
             } else {
-                return;
+                // don't install a ValueFacet
+                return Optional.empty();
             }
         } else {
             log.debug("found {} ValueSemanticsProvider(s) for value type {}", semanticsProviders.size(), valueClass);
@@ -128,6 +134,15 @@ extends FacetFactoryAbstract {
         addFacetIfPresent(MaxLengthFacetFromValueFacet.create(valueFacet, holder));
         addFacetIfPresent(DefaultedFacetFromValueFacet.create(valueFacet, holder));
 
+        return Optional.of(valueFacet);
+    }
+
+    // optimization
+    private void emitValueFacetProcessed(
+            final FacetHolder holder,
+            final Optional<ValueFacet> valueFacetIfAny) {
+        _Casts.castTo(ObjectSpecificationDefault.class, holder)
+        .ifPresent(receiver->receiver.onValueFacetProcessed(valueFacetIfAny));
     }
 
     // -- DEPENDENCIES
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ActionInteraction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ActionInteraction.java
index 750c55d0d8..2d3a25bd78 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ActionInteraction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ActionInteraction.java
@@ -203,7 +203,7 @@ extends MemberInteraction<ManagedAction, ActionInteraction> {
         val elementType = prop.getElementType();
 
         val valueFacet = elementType.isValue()
-                ? (ValueFacet<?>) elementType.getFacet(ValueFacet.class)
+                ? (ValueFacet<?>) elementType.valueFacet().orElse(null)
                 : null;
 
         if(valueFacet!=null
@@ -242,7 +242,7 @@ extends MemberInteraction<ManagedAction, ActionInteraction> {
         val elementType = param.getMetaModel().getElementType();
 
         val valueFacet = elementType.isValue()
-                ? (ValueFacet<?>) elementType.getFacet(ValueFacet.class)
+                ? (ValueFacet<?>) elementType.valueFacet().orElse(null)
                 : null;
         if(valueFacet!=null
                 && valueFacet.isCompositeValueType()
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/_BindingUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/_BindingUtil.java
index 2fd24dcf5d..3a983255b4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/_BindingUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/_BindingUtil.java
@@ -27,7 +27,6 @@ import org.apache.isis.commons.internal.binding._BindableAbstract;
 import org.apache.isis.commons.internal.binding._Bindables;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -63,7 +62,7 @@ class _BindingUtil {
         val spec = prop.getElementType();
 
         // value types should have associated parsers/formatters via value semantics
-        return spec.lookupFacet(ValueFacet.class)
+        return spec.valueFacet()
         .map(valueFacet->{
             val eitherRendererOrParser = format.requiresRenderer()
                 ? Either.<Renderer, Parser>left(valueFacet.selectRendererForPropertyElseFallback(prop))
@@ -92,7 +91,7 @@ class _BindingUtil {
         val spec = param.getElementType();
 
         // value types should have associated parsers/formatters via value semantics
-        return spec.lookupFacet(ValueFacet.class)
+        return spec.valueFacet()
         .map(valueFacet->{
             val eitherRendererOrParser = format.requiresRenderer()
                 ? Either.<Renderer, Parser>left(valueFacet.selectRendererForParameterElseFallback(param))
@@ -114,7 +113,7 @@ class _BindingUtil {
 
     boolean hasParser(final @NonNull OneToOneAssociation prop) {
         return prop.getElementType()
-                .lookupFacet(ValueFacet.class)
+                .valueFacet()
                 .map(valueFacet->valueFacet.selectRendererForProperty(prop).isPresent())
                 .orElse(false);
     }
@@ -123,7 +122,7 @@ class _BindingUtil {
         return isNonScalarParam(param)
                 ? false
                 : param.getElementType()
-                    .lookupFacet(ValueFacet.class)
+                    .valueFacet()
                     .map(valueFacet->valueFacet.selectRendererForParameter(param).isPresent())
                     .orElse(false);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
index d6d2a01382..7236fb567f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
@@ -30,7 +30,6 @@ import org.apache.isis.commons.internal.debug._Debug;
 import org.apache.isis.commons.internal.debug.xray.XrayUi;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
 import org.apache.isis.core.metamodel.objectmanager.identify.ObjectBookmarker.Handler;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -133,7 +132,7 @@ class ObjectBookmarker_builtinHandlers {
 
         @Override
         public boolean isHandling(final ManagedObject managedObject) {
-            return managedObject.getSpecification().containsFacet(ValueFacet.class);
+            return managedObject.getSpecification().isValue();
         }
 
         @SneakyThrows
@@ -145,7 +144,7 @@ class ObjectBookmarker_builtinHandlers {
                 return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), "{}");
             }
 
-            val valueFacet = spec.getFacet(ValueFacet.class);
+            val valueFacet = spec.valueFacet().orElse(null);
             ValueSemanticsProvider<Object> composer = (ValueSemanticsProvider) valueFacet.selectDefaultSemantics()
                     .orElseThrow(()->_Exceptions.illegalArgument(
                             "Cannot create a bookmark for the value type %s, "
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
index a4e34f2443..d57199aa03 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObject.java
@@ -35,7 +35,6 @@ import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.facets.object.title.TitleRenderRequest;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -157,7 +156,7 @@ public interface ManagedObject {
         }
 
         val spec = getSpecification();
-        val valueFacet = spec.getFacet(ValueFacet.class);
+        val valueFacet = spec.valueFacet().orElse(null);
 
         if(valueFacet==null) {
             return String.format("missing ValueFacet %s", spec.getCorrespondingClass());
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index 13b501de40..109cccd6dd 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -32,7 +32,6 @@ import org.springframework.lang.Nullable;
 import org.apache.isis.applib.exceptions.UnrecoverableException;
 import org.apache.isis.applib.id.HasLogicalType;
 import org.apache.isis.applib.services.metamodel.BeanSort;
-import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.collections._Streams;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
@@ -59,7 +58,6 @@ import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionF
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleRenderRequest;
 import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacetAbstract;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
 import org.apache.isis.core.metamodel.interactions.InteractionContext;
 import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
@@ -69,7 +67,6 @@ import org.apache.isis.core.metamodel.objectmanager.create.ObjectCreator;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationContainer;
-import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.specloader.specimpl.IntrospectionState;
 import org.apache.isis.core.metamodel.specloader.specimpl.MixedInMember;
@@ -353,7 +350,7 @@ extends
      */
     default boolean isValue() {
         return getBeanSort().isValue()
-                || containsFacet(ValueFacet.class);
+                || valueFacet().isPresent();
     }
 
     /**
@@ -560,7 +557,7 @@ extends
     }
 
     default String fqcn() {
-        return  getCorrespondingClass().getName();
+        return getCorrespondingClass().getName();
     }
 
     default Stream<ObjectSpecification> streamTypeHierarchy() {
@@ -571,21 +568,8 @@ extends
 
     // -- VALUE SEMANTICS SUPPORT
 
-    @SuppressWarnings("unchecked")
-    default Optional<Parser<?>> selectParser(final ObjectFeature objFeature) {
-        return lookupFacet(ValueFacet.class)
-                .flatMap(valueFacet->valueFacet.selectParserForFeature(objFeature));
-    }
-
-    default Parser<?> selectParserElseFallback(final ObjectFeature objFeature) {
-        return lookupFacet(ValueFacet.class)
-                .map(valueFacet->
-                        valueFacet.selectParserForFeatureElseFallback(objFeature))
-                .orElseGet(()->ValueFacetAbstract.fallbackParser(
-                        getLogicalType(),
-                        objFeature.getFeatureIdentifier()));
-    }
-
-
+    /** introduced for lookup optimization / allow memoization */
+    @SuppressWarnings("rawtypes")
+    Optional<ValueFacet> valueFacet();
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAssociation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAssociation.java
index fb1603f672..4d94e0e04f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAssociation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAssociation.java
@@ -34,7 +34,6 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facets.WhereValueFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
 import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFacet;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderComparator;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
@@ -132,8 +131,8 @@ public interface ObjectAssociation extends ObjectMember, CurrentHolder {
                 assoc -> assoc.isOneToOneAssociation();
 
         public static final Predicate<ObjectAssociation> REFERENCE_PROPERTIES =
-                assoc ->  assoc.isOneToOneAssociation() &&
-                         !assoc.getElementType().containsNonFallbackFacet(ValueFacet.class);
+                assoc ->  assoc.isOneToOneAssociation()
+                            && !assoc.getElementType().isValue();
 
         public static final Predicate<ObjectAssociation> COLLECTIONS =
                 assoc -> assoc.isOneToManyAssociation();
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index a24f3842b8..dff41e973d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -141,7 +141,7 @@ implements FacetHolder {
         addNamedFacetIfRequired();
 
         // go no further if a value
-        if(this.containsFacet(ValueFacet.class)) {
+        if(this.isValue()) {
             if (log.isDebugEnabled()) {
                 log.debug("skipping type hierarchy introspection for value type {}", getFullIdentifier());
             }
@@ -180,7 +180,7 @@ implements FacetHolder {
     @Override
     protected void introspectMembers() {
 
-        if(this.containsFacet(ValueFacet.class)) {
+        if(this.isValue()) {
             if (log.isDebugEnabled()) {
                 log.debug("skipping full introspection for value type {}", getFullIdentifier());
             }
@@ -354,7 +354,8 @@ implements FacetHolder {
 
     // -- ELEMENT SPECIFICATION
 
-    private final _Lazy<Optional<ObjectSpecification>> elementSpecification = _Lazy.of(this::lookupElementSpecification);
+    private final _Lazy<Optional<ObjectSpecification>> elementSpecification =
+            _Lazy.of(this::lookupElementSpecification);
 
     @Override
     public Optional<ObjectSpecification> getElementSpecification() {
@@ -362,7 +363,7 @@ implements FacetHolder {
     }
 
     private Optional<ObjectSpecification> lookupElementSpecification() {
-        return Optional.ofNullable(getFacet(TypeOfFacet.class))
+        return lookupFacet(TypeOfFacet.class)
                 .map(typeOfFacet -> ElementSpecificationProvider.of(typeOfFacet).getElementType());
     }
 
@@ -377,5 +378,25 @@ implements FacetHolder {
             .streamPropertiesForColumnRendering(this, memberIdentifier, parentObject);
     }
 
+    // -- VALUE FACET OPTIMIZATION (MEMOIZATION)
+
+    // not thread-safe, but seems ok for caching
+    @SuppressWarnings("rawtypes")
+    private Optional<ValueFacet> valueFacetMemoized = null;
+
+    @SuppressWarnings("rawtypes")
+    public void onValueFacetProcessed(final Optional<ValueFacet> valueFacetIfAny) {
+        valueFacetMemoized = valueFacetIfAny;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Optional<ValueFacet> valueFacet() {
+        return valueFacetMemoized != null
+                ? valueFacetMemoized
+                : lookupFacet(ValueFacet.class);
+    }
+
+    // --
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/Facets.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/Facets.java
index 752ae0ef2b..a21a6c9a3f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/Facets.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/Facets.java
@@ -330,13 +330,9 @@ public final class Facets {
 
     // -- VALUE FACET
 
-    public boolean valueIsPresent(final ObjectSpecification objectSpec) {
-        return objectSpec.containsFacet(ValueFacet.class);
-    }
-
     public static Predicate<ObjectSpecification> valueTypeMatches(final Predicate<Class<?>> typeMatcher) {
         return spec->
-            spec.lookupFacet(ValueFacet.class)
+            spec.valueFacet()
             .map(ValueFacet::getLogicalType)
             .map(LogicalType::getCorrespondingClass)
             .map(typeMatcher::test)
@@ -349,8 +345,7 @@ public final class Facets {
             final ParameterNegotiationModel parameterNegotiationModel,
             final int paramIndex) {
         val objectSpec = param.getElementType();
-        //if(!objectSpec.isValue()) return Optional.empty(); // optimization
-        return objectSpec.lookupFacet(ValueFacet.class)
+        return objectSpec.valueFacet()
         .<ObjectAction>flatMap(valueFacet->
             valueFacet.selectCompositeValueMixinForParameter(
                     parameterNegotiationModel,paramIndex));
@@ -361,8 +356,7 @@ public final class Facets {
             final ObjectFeature prop,
             final ManagedProperty managedProperty) {
         val objectSpec = prop.getElementType();
-        //if(!objectSpec.isValue()) return Optional.empty(); // optimization
-        return objectSpec.lookupFacet(ValueFacet.class)
+        return objectSpec.valueFacet()
         .<ObjectAction>flatMap(valueFacet->
             valueFacet.selectCompositeValueMixinForProperty(managedProperty));
     }
@@ -370,26 +364,36 @@ public final class Facets {
     @SuppressWarnings("unchecked")
     public <X> Stream<X> valueStreamSemantics(
             final ObjectSpecification objectSpec, final Class<X> requiredType) {
-        //if(!objectSpec.isValue()) return Stream.empty(); // optimization
-        return objectSpec.lookupFacet(ValueFacet.class)
+        return objectSpec.valueFacet()
         .map(valueFacet->valueFacet.streamValueSemantics(requiredType))
         .orElseGet(Stream::empty);
     }
 
+    @SuppressWarnings("unchecked")
+    public <X> boolean valueHasSemantics(
+            final ObjectSpecification objectSpec, final Class<X> requiredType) {
+        return objectSpec.valueFacet()
+        .map(valueFacet->valueFacet.streamValueSemantics(requiredType)
+                .findFirst()
+                .isPresent())
+        .orElse(false);
+    }
+
     @SuppressWarnings("unchecked")
     public <X> Optional<ValueSemanticsProvider<X>> valueDefaultSemantics(
             final ObjectSpecification objectSpec,
             final Class<X> requiredType) {
-        return objectSpec.lookupFacet(ValueFacet.class)
+        return objectSpec.valueFacet()
         .filter(valueFacet->requiredType.isAssignableFrom(valueFacet.getValueClass()))
-        .flatMap(ValueFacet::selectDefaultSemantics);
+        .flatMap(ValueFacet::selectDefaultSemantics)
+        .map(_Casts::uncheckedCast);
     }
 
     @SuppressWarnings("unchecked")
     public <X> Optional<ValueSerializer<X>> valueSerializer(
             final ObjectSpecification objectSpec,
             final Class<X> requiredType) {
-        return objectSpec.lookupFacet(ValueFacet.class)
+        return objectSpec.valueFacet()
         .filter(valueFacet->requiredType.isAssignableFrom(valueFacet.getValueClass()))
         .map(valueFacet->(ValueSerializer<X>)valueFacet);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java
index ea2f5a25f1..19a670ceff 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java
@@ -46,7 +46,6 @@ import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facets.collections.CollectionFacet;
-import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer.Format;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
@@ -678,7 +677,7 @@ public class XmlSnapshot implements Snapshot {
 
             Element xsdFieldElement = null;
 
-            if (field.getElementType().containsFacet(ValueFacet.class)) {
+            if (field.getElementType().isValue()) {
                 if (log.isDebugEnabled()) {
                     log.debug("objectToElement(NO): {} is value", log("field", fieldName));
                 }
@@ -709,7 +708,7 @@ public class XmlSnapshot implements Snapshot {
 
                     // return encoded string, else title.
                     String valueStr;
-                    val valueFacet = fieldNos.getFacet(ValueFacet.class);
+                    val valueFacet = fieldNos.valueFacet().orElse(null);
                     if (valueFacet != null) {
                         valueStr = valueFacet.toEncodedString(Format.JSON, value.getPojo());
                     } else {
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index 166437661a..0de46cd282 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -30,6 +30,7 @@ import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.services.metamodel.BeanSort;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.collections.ImmutableEnumSet;
+import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
@@ -44,6 +45,7 @@ import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.logicaltype.LogicalTypeFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleRenderRequest;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
 import org.apache.isis.core.metamodel.interactions.ObjectValidityContext;
 import org.apache.isis.core.metamodel.spec.ActionScope;
@@ -392,5 +394,9 @@ implements ObjectSpecification {
                         .noneMatch(where -> where.includes(whereContext)));
     }
 
+    @Override
+    public Optional<ValueFacet> valueFacet() {
+        return _Casts.uncheckedCast(lookupFacet(ValueFacet.class));
+    }
 
 }
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
index 50bd73f2dc..3689a29786 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java
@@ -132,7 +132,7 @@ public class ObjectAndActionInvocation {
 
     //TODO[2449] need to check whether that strategy holds consistently
     private static boolean isScalarValue(final @NonNull ObjectSpecification spec) {
-        return Facets.valueIsPresent(spec);
+        return spec.isValue();
     }
 
     private static boolean isVector(final @NonNull ObjectSpecification spec) {
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
index 2de8f6ceb2..dab4e5d671 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
@@ -90,7 +90,7 @@ extends AbstractObjectMemberReprRenderer<OneToOneAssociation> {
 
         val spec = valueAdapter.getSpecification();
 
-        if (Facets.valueIsPresent(spec)) {
+        if (spec.isValue()) {
             String format = null;
             final Class<?> valueType = spec.getCorrespondingClass();
             if(valueType == java.math.BigDecimal.class) {
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
index 83b6e4444f..cec006c73e 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
@@ -22,7 +22,6 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext;
@@ -56,7 +55,7 @@ extends ReprRendererAbstract<ManagedObject> {
 
     @Override
     public ScalarValueReprRenderer with(final ManagedObject objectAdapter) {
-        if (!Facets.valueIsPresent(objectAdapter.getSpecification())) {
+        if (!objectAdapter.getSpecification().isValue()) {
             throw ReprRendererException.create("Not an (encodable) value", objectAdapter.titleString());
         }
         String format = null; // TODO
diff --git a/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_asAdapter.java b/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_asAdapter.java
index 99d2f4881d..c2d8ed0ef1 100644
--- a/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_asAdapter.java
+++ b/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_asAdapter.java
@@ -383,6 +383,10 @@ public class JsonValueEncoderTest_asAdapter {
     private void allowingObjectSpecHasValue(final Class<?> valueClass) {
         context.checking(new Expectations() {
             {
+
+                allowing(mockObjectSpec).valueFacet();
+                will(returnValue(Optional.of(mockValueFacet)));
+
                 allowing(mockObjectSpec).getFacet(ValueFacet.class);
                 will(returnValue(mockValueFacet));
 
@@ -399,6 +403,10 @@ public class JsonValueEncoderTest_asAdapter {
     private <T extends Facet> void allowingObjectSpecHas(final Class<T> facetClass, final T facet) {
         context.checking(new Expectations() {
             {
+
+                allowing(mockObjectSpec).valueFacet();
+                will(returnValue(Optional.ofNullable(facet)));
+
                 allowing(mockObjectSpec).getFacet(facetClass);
                 will(returnValue(facet));
 
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/converter/ConverterBasedOnValueSemantics.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/converter/ConverterBasedOnValueSemantics.java
index 30e62fcd23..2fd6bd668b 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/converter/ConverterBasedOnValueSemantics.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/converter/ConverterBasedOnValueSemantics.java
@@ -150,12 +150,12 @@ implements
     private ValueFacet<T> valueFacet() {
         val feature = feature();
         val valueFacet = feature.getElementType()
-                .lookupFacet(ValueFacet.class)
+                .valueFacet()
                 .orElseThrow(()->_Exceptions.noSuchElement(
                         "Value type Property or Parameter %s is missing a ValueFacet",
                         feature.getFeatureIdentifier()));
 
-        return valueFacet;
+        return (ValueFacet<T>) valueFacet;
     }
 
     // -- DEPENDENCIES
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
index 22834311d9..05f93c4e3c 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
@@ -21,8 +21,6 @@ package org.apache.isis.viewer.wicket.ui.components.entity;
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.viewer.common.model.components.ComponentType;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
 import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
@@ -68,11 +66,9 @@ public abstract class EntityComponentFactoryAbstract extends ComponentFactoryAbs
         if (adapter == null) {
             // is ok;
         }
-        final ObjectSpecification specification = entityModel.getTypeOfSpecification();
-        final boolean isScalar = specification.isScalar();
-
-        final boolean isValue = Facets.valueIsPresent(specification);
-        if (isScalar && !isValue) {
+        val spec = entityModel.getTypeOfSpecification();
+        if (spec.isScalar()
+                && !spec.isValue()) {
             return doAppliesTo(entityModel);
         }
         return ApplicationAdvice.DOES_NOT_APPLY;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
index 5490b74095..98b293fbc7 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
@@ -21,12 +21,14 @@ package org.apache.isis.viewer.wicket.ui.components.scalars.image;
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.core.metamodel.valuesemantics.ImageValueSemantics;
 import org.apache.isis.viewer.common.model.components.ComponentType;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
 import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
 
+import lombok.val;
+
 public class JavaAwtImagePanelFactory extends ComponentFactoryAbstract {
 
     private static final long serialVersionUID = 1L;
@@ -40,15 +42,15 @@ public class JavaAwtImagePanelFactory extends ComponentFactoryAbstract {
         if (!(model instanceof ScalarModel)) {
             return ApplicationAdvice.DOES_NOT_APPLY;
         }
-        final ScalarModel scalarModel = (ScalarModel) model;
-        final ObjectSpecification specification = scalarModel.getScalarTypeSpec();
-        return appliesIf(specification != null
-                && specification.hasValueSemantics(ImageValueSemantics.class));
+        val scalarModel = (ScalarModel) model;
+        val typeSpec = scalarModel.getScalarTypeSpec();
+        return appliesIf(typeSpec != null
+                && Facets.valueHasSemantics(typeSpec, ImageValueSemantics.class));
     }
 
     @Override
     public Component createComponent(final String id, final IModel<?> model) {
-        final ScalarModel scalarModel = (ScalarModel) model;
+        val scalarModel = (ScalarModel) model;
         return new JavaAwtImagePanel(id, scalarModel);
     }
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/WicketImageUtil.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/WicketImageUtil.java
index 0e54e42a49..11f75c0965 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/WicketImageUtil.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/image/WicketImageUtil.java
@@ -31,6 +31,7 @@ import org.apache.isis.applib.value.Blob;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.core.metamodel.valuesemantics.ImageValueSemantics;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
 
@@ -82,9 +83,9 @@ public class WicketImageUtil {
           return Optional.empty();
       }
 
-      val spec = model.getScalarTypeSpec();
+      val typeSpec = model.getScalarTypeSpec();
 
-      return spec.streamValueSemantics(ImageValueSemantics.class)
+      return Facets.valueStreamSemantics(typeSpec, ImageValueSemantics.class)
       .map(imageValueSemantics->imageValueSemantics.getImage(adapter).orElse(null))
       .filter(_NullSafe::isPresent)
       .map(buffImg->asWicketImage(id, buffImg).orElse(null))
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
index 5598601e0b..338fcbf671 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
@@ -21,11 +21,12 @@ package org.apache.isis.viewer.wicket.ui.components.scalars.value.fallback;
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 
-import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
 import org.apache.isis.viewer.wicket.ui.ComponentFactory;
 import org.apache.isis.viewer.wicket.ui.components.scalars.ComponentFactoryScalarAbstract;
 
+import lombok.val;
+
 /**
  * {@link ComponentFactory} for the {@link ValueFallbackPanel}.
  */
@@ -43,8 +44,8 @@ extends ComponentFactoryScalarAbstract {
         if (!(model instanceof ScalarModel)) {
             return ApplicationAdvice.DOES_NOT_APPLY;
         }
-        final ScalarModel scalarModel = (ScalarModel) model;
-        if(!Facets.valueIsPresent(scalarModel.getScalarTypeSpec())) {
+        val scalarModel = (ScalarModel) model;
+        if(!scalarModel.getScalarTypeSpec().isValue()) {
             return ApplicationAdvice.DOES_NOT_APPLY;
         }
         final boolean hasChoices = scalarModel.hasChoices();