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/09/02 08:04:47 UTC

[isis] branch 3200_broken-value-choices created (now 4174060ff1)

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

ahuber pushed a change to branch 3200_broken-value-choices
in repository https://gitbox.apache.org/repos/asf/isis.git


      at 4174060ff1 ISIS-3200: ManagedObjects of type VALUE should provide bookmarks themselves

This branch includes the following new commits:

     new 4174060ff1 ISIS-3200: ManagedObjects of type VALUE should provide bookmarks themselves

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[isis] 01/01: ISIS-3200: ManagedObjects of type VALUE should provide bookmarks themselves

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch 3200_broken-value-choices
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 4174060ff1a11d87e14677bdc7fbf93894934ef0
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Sep 2 10:04:38 2022 +0200

    ISIS-3200: ManagedObjects of type VALUE should provide bookmarks
    themselves
    
    - also introduce URL_SAFE semantics in the context of value serializers
---
 .../isis/applib/services/bookmark/Bookmark.java    |  14 +-
 .../images/managed-object-diagram.drawio.svg       |   2 +-
 .../facets/object/entity/EntityFacet.java          |  36 ++++++
 .../facets/object/value/ValueFacetAbstract.java    |  10 +-
 .../facets/object/value/ValueSerializer.java       |   9 +-
 .../object/value/ValueSerializerDefault.java       |  32 +++--
 .../object/viewmodel/ViewModelFacetAbstract.java   |   4 +-
 .../isis/core/metamodel/object/ManagedObject.java  |  14 +-
 .../isis/core/metamodel/object/ManagedObjects.java |  11 +-
 .../metamodel/object/_ManagedObjectService.java    |   4 +-
 .../core/metamodel/object/_ManagedObjectValue.java |   9 +-
 .../metamodel/objectmanager/ObjectManager.java     |  18 +--
 .../objectmanager/identify/ObjectBookmarker.java   |   1 +
 .../identify/ObjectBookmarker_builtinHandlers.java |  70 +++++-----
 .../metamodel/spec/HasObjectSpecification.java     |  60 +++++++++
 .../core/metamodel/util/snapshot/XmlSnapshot.java  |  26 ++--
 .../value/JavaTimeValueSemanticsProviderTest.java  |  11 +-
 .../ValueSemanticsProviderAbstractTestCase.java    |  41 +++---
 .../isis/core/runtime/IsisModuleCoreRuntime.java   |   4 +-
 ...ervice.java => IdStringifierLookupService.java} |  42 ++----
 .../command/CommandDtoFactoryDefault.java          |   3 +-
 .../interaction/InteractionDtoFactoryDefault.java  |  13 +-
 .../memento/ObjectMementoServiceDefault.java       |   6 +-
 .../runtimeservices/memento/_ObjectMemento.java    | 144 +++++++++------------
 .../entities/DnEntityStateProvider.java            |  10 +-
 .../metamodel/facets/entity/JdoEntityFacet.java    |  41 ++++--
 .../jpa/integration/entity/JpaEntityFacet.java     |  32 +++--
 .../integration/entity/JpaEntityFacetFactory.java  |   2 +-
 .../JsonValueEncoderServiceDefault.java            |   4 +-
 .../wicket/model/util/PageParameterUtils.java      |  16 ---
 30 files changed, 377 insertions(+), 312 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java
index c23d21eb6c..5d7036628a 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java
@@ -75,19 +75,19 @@ public final class Bookmark implements Oid {
 
     public static Bookmark forLogicalTypeNameAndIdentifier(
             final @NonNull String logicalTypeName,
-            final @NonNull String identifier) {
+            final @NonNull String urlSafeIdentifier) {
         return new Bookmark(
                 logicalTypeName,
-                identifier,
+                urlSafeIdentifier,
                 /*hintId*/null);
     }
 
     public static Bookmark forLogicalTypeAndIdentifier(
             final @NonNull LogicalType logicalType,
-            final @NonNull String identifier) {
+            final @NonNull String urlSafeIdentifier) {
         return Bookmark.forLogicalTypeNameAndIdentifier(
                 logicalType.getLogicalTypeName(),
-                identifier);
+                urlSafeIdentifier);
     }
 
     public static Bookmark forOidDto(final @NonNull OidDto oidDto) {
@@ -104,12 +104,12 @@ public final class Bookmark implements Oid {
 
     private Bookmark(
             final String logicalTypeName,
-            final String identifier,
+            final String urlSafeIdentifier,
             final String hintId) {
         this.logicalTypeName = logicalTypeName;
-        this.identifier = identifier;
+        this.identifier = urlSafeIdentifier;
         this.hintId = hintId;
-        this.hashCode = Objects.hash(logicalTypeName, identifier);
+        this.hashCode = Objects.hash(logicalTypeName, urlSafeIdentifier);
     }
 
     // -- PARSE
diff --git a/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg b/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg
index bd01036df0..55aa26123a 100644
--- a/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg
+++ b/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Do not edit this file with editors other than diagrams.net -->
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1391px" height="591px" viewBox="-0.5 -0.5 1391 591" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2022-09-01T14:29:56.898Z&quot; agent=&quot;5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.2.3 Chrome/102.0.5005.167 Electron/19.0.11 Safari/537.36&quot; etag=&quot;cwu7jfOeW0fUMNdx5SSP&quot; version=&quot;20.2.3&quot; type=&quot;device&qu [...]
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1391px" height="591px" viewBox="-0.5 -0.5 1391 591" content="&lt;mxfile host=&quot;Electron&quot; modified=&quot;2022-09-02T03:02:42.988Z&quot; agent=&quot;5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.2.3 Chrome/102.0.5005.167 Electron/19.0.11 Safari/537.36&quot; etag=&quot;lW1AIfm3Z-gm0gO0WKht&quot; version=&quot;20.2.3&quot; type=&quot;device&qu [...]
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java
index f3537fb078..9daafec0df 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java
@@ -20,13 +20,17 @@ package org.apache.isis.core.metamodel.facets.object.entity;
 
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Function;
 
 import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
 
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.bookmark.IdStringifier;
 import org.apache.isis.applib.services.repository.EntityState;
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.config.beans.PersistenceStack;
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -35,12 +39,44 @@ import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.object.MmSpecUtil;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
+import lombok.NonNull;
+
 /**
  * Indicates that this class is managed by a persistence context.
  * @since 2.0
  */
 public interface EntityFacet extends Facet {
 
+    @lombok.Value(staticConstructor = "of")
+    static class PrimaryKeyType<T> {
+        private final @NonNull Class<?> owningEntityClass;
+        private final @NonNull IdStringifier<T> idStringifier;
+        private final @NonNull Class<T> primaryKeyClass;
+        public String enstring(final T primaryKey) {
+            return idStringifier.enstring(primaryKey);
+        }
+        public String enstringWithCast(final Object primaryKey) {
+            return _Casts.castTo(primaryKeyClass, primaryKey)
+            .map(idStringifier::enstring)
+            .orElseThrow(()->_Exceptions.illegalArgument(
+                    "failed to cast primary-key '%s' to expected type %s",
+                        ""+primaryKey,
+                        primaryKeyClass.getName()));
+        }
+        public T destring(final String stringifiedPrimaryKey) {
+            return idStringifier.destring(owningEntityClass, stringifiedPrimaryKey);
+        }
+        public static <T> PrimaryKeyType<T> getInstance(
+                final @NonNull Class<?> owningEntityClass,
+                final @NonNull Function<Class<T>, IdStringifier<T>> stringifierLookup,
+                final @NonNull Class<T> primaryKeyClass){
+            return of(
+                    owningEntityClass,
+                    stringifierLookup.apply(primaryKeyClass),
+                    _Casts.uncheckedCast(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyClass)));
+        }
+    }
+
     /**
      * The {@link ObjectSpecification} of the entity type this
      * facet is associated with.
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
index 3c402b246a..9ba54f5acb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
@@ -132,16 +132,16 @@ implements ValueFacet<T> {
                 iaProvider.currentInteractionContext().orElse(null));
     }
 
-    // -- TO STRING SERIALIZATION
+    // -- TO/FROM STRING SERIALIZATION
 
     @Override
-    public T fromEncodedString(final Format format, final String encodedData) {
-        return valueSerializer.fromEncodedString(format, encodedData);
+    public final T destring(final Format format, final String encodedData) {
+        return valueSerializer.destring(format, encodedData);
     }
 
     @Override
-    public String toEncodedString(final Format format, final T value) {
-        return valueSerializer.toEncodedString(format, value);
+    public final String enstring(final Format format, final T value) {
+        return valueSerializer.enstring(format, value);
     }
 
     // -- ORDER RELATION
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java
index a3ee0d05fc..772e8d3429 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java
@@ -18,21 +18,26 @@
  */
 package org.apache.isis.core.metamodel.facets.object.value;
 
+import org.springframework.lang.Nullable;
+
+import lombok.NonNull;
+
 public interface ValueSerializer<T> {
 
     public enum Format {
         JSON,
+        URL_SAFE,
         //XML
     }
 
     /**
      * Converts a string of provided {@link Format} to an instance of the object.
      */
-    T fromEncodedString(Format format, String encodedData);
+    T destring(@NonNull Format format, @NonNull String encodedData);
 
     /**
      * Converts the provided object into provided {@link Format}.
      */
-    String toEncodedString(Format format, T value);
+    String enstring(@NonNull Format format, @Nullable T value);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java
index 8d07fd0212..94f3c644db 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java
@@ -18,10 +18,13 @@
  */
 package org.apache.isis.core.metamodel.facets.object.value;
 
+import org.springframework.lang.Nullable;
+
 import org.apache.isis.applib.value.semantics.ValueDecomposition;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
-import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 
 import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
@@ -35,21 +38,34 @@ implements ValueSerializer<T> {
     private final @NonNull ValueSemanticsProvider<T> semantics;
 
     @Override
-    public T fromEncodedString(final Format format, final String encodedData) {
-        _Assert.assertNotNull(encodedData);
+    public T destring(final @NonNull Format format, final @NonNull String encodedData) {
         if (ENCODED_NULL.equals(encodedData)) {
             return null;
-        } else {
+        }
+        switch(format) {
+        case JSON:
             return semantics.compose(
                     ValueDecomposition.fromJson(semantics.getSchemaValueType(), encodedData));
+        case URL_SAFE:
+            //TODO could use IdStringifiers instead
+            return destring(Format.JSON, _Strings.base64UrlDecode(encodedData));
         }
+        throw _Exceptions.unmatchedCase(format);
     }
 
     @Override
-    public String toEncodedString(final Format format, final T value) {
-        return value == null
-                ? ENCODED_NULL
-                : semantics.decompose(_Casts.uncheckedCast(value)).toJson();
+    public String enstring(final @NonNull Format format, final @Nullable T value) {
+        if(value == null) {
+            return ENCODED_NULL;
+        }
+        switch(format) {
+        case JSON:
+            return semantics.decompose(_Casts.uncheckedCast(value)).toJson();
+        case URL_SAFE:
+            //TODO could use IdStringifiers instead
+            return _Strings.base64UrlEncode(enstring(Format.JSON, value));
+        }
+        throw _Exceptions.unmatchedCase(format);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java
index 8c3c1d8328..32c153b06e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java
@@ -101,9 +101,7 @@ implements ViewModelFacet {
 
     @Override
     public final Bookmark serializeToBookmark(final @NonNull ManagedObject managedObject) {
-        return Bookmark.forLogicalTypeAndIdentifier(
-                managedObject.getSpecification().getLogicalType(),
-                serialize(managedObject));
+        return managedObject.createBookmark(serialize(managedObject));
     }
 
     protected abstract @NonNull String serialize(@NonNull ManagedObject managedObject);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java
index 8997ef491a..ef2646d3af 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java
@@ -30,6 +30,7 @@ import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.context.HasMetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon;
 import org.apache.isis.core.metamodel.object.ManagedObject.Specialization.BookmarkPolicy;
+import org.apache.isis.core.metamodel.spec.HasObjectSpecification;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
@@ -50,7 +51,8 @@ import lombok.extern.log4j.Log4j2;
 public interface ManagedObject
 extends
     Bookmarkable,
-    HasMetaModelContext {
+    HasMetaModelContext,
+    HasObjectSpecification {
 
     /**
      * ManagedObject specializations have varying contract/behavior.
@@ -316,6 +318,7 @@ extends
     /**
      * Returns the specification that details the structure (meta-model) of this object.
      */
+    @Override
     ObjectSpecification getSpecification();
 
     /**
@@ -351,15 +354,6 @@ extends
      */
     String getTitle();
 
-    // -- SHORTCUT - ELEMENT SPECIFICATION
-
-    /**
-     * As used for the element type of collections.
-     */
-    default Optional<ObjectSpecification> getElementSpecification() {
-        return getSpecification().getElementSpecification();
-    }
-
     // -- SHORTCUT - ICON
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java
index ee6f7b2be3..f642afe7db 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.util.Comparator;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.UUID;
 import java.util.function.Supplier;
 
 import org.springframework.lang.Nullable;
@@ -161,6 +162,14 @@ public final class ManagedObjects {
                 .orElseThrow(()->_Exceptions.illegalArgument("cannot identify %s", managedObject));
     }
 
+    /**
+     * eg. transient entities have no bookmark, so can fallback to UUID
+     */
+    public static Bookmark bookmarkElseUUID(final @Nullable ManagedObject managedObject) {
+        return bookmark(managedObject)
+                .orElseGet(()->managedObject.createBookmark(UUID.randomUUID().toString()));
+    }
+
     /**
      * @param managedObject
      * @return optionally a String representing a reference to the <em>identifiable</em>
@@ -368,7 +377,7 @@ public final class ManagedObjects {
         .collect(Can.toCan());
     }
 
-    
+
 
     // -- IMPERATIVE TEXT UTILITY
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java
index fd1f168a9f..57e409f01c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java
@@ -80,9 +80,7 @@ extends _ManagedObjectSpecified {
     // -- HELPER
 
     private Bookmark createBookmark() {
-        return Bookmark.forLogicalTypeAndIdentifier(
-                getSpecification().getLogicalType(),
-                "1");
+        return createBookmark("1");
     }
 
 }
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java
index f6795095af..4bcabc9681 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java
@@ -77,15 +77,12 @@ extends _ManagedObjectSpecified {
     // -- HELPER
 
     private ValueFacet<?> valueFacet() {
-        return getSpecification().valueFacet().orElseThrow();
+        return getSpecification().valueFacetElseFail();
     }
 
     private Bookmark createBookmark() {
-        //TODO if value semantics providers are enforced to provide an IdStringifier,
-        // we could use that instead (to generate the second argument)!
-        return Bookmark.forLogicalTypeAndIdentifier(
-                getSpecification().getLogicalType(),
-                valueFacet().toEncodedString(Format.JSON, _Casts.uncheckedCast(getPojo())));
+        return createBookmark(
+                valueFacet().enstring(Format.URL_SAFE, _Casts.uncheckedCast(getPojo())));
     }
 
 }
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
index 165b141cfa..bf1229edf1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
@@ -142,7 +142,7 @@ public interface ObjectManager {
             return ManagedObject.unspecified();
         }
         return spec.isScalar()
-                ? managedObjectEagerlyBookmarkedIfRequired(spec, pojo)
+                ? ManagedObject.adaptScalar(spec, pojo)
                 : ManagedObject.packed(
                         spec.getElementSpecification().orElseGet(fallbackElementType),
                         _NullSafe.streamAutodetect(pojo)
@@ -173,25 +173,11 @@ public interface ObjectManager {
                 || pojo.getClass().equals(proposedSpec.getCorrespondingClass()))
             // if actual type matches spec's, we assume, that we don't need to reload,
             // so this is a shortcut for performance reasons
-            ? managedObjectEagerlyBookmarkedIfRequired(
-                    proposedSpec, pojo)
+            ? ManagedObject.adaptScalar(proposedSpec, pojo)
             // fallback, ignoring proposedSpec
             : adapt(pojo);
         return adapter;
     }
 
-    // -- HELPER
-
-    /**
-     * {@link ManagedObject} factory, that in case of given pojo representing an entity
-     * and the entityAdaptingMode equals {@link EntityAdaptingMode#isBookmarkable()},
-     * then tries to memoize its {@link Bookmark} eagerly
-     * (otherwise its {@link Bookmark} is lazily resolved).
-     */
-    private static ManagedObject managedObjectEagerlyBookmarkedIfRequired(
-            final ObjectSpecification spec,
-            final Object pojo) {
-        return ManagedObject.adaptScalar(spec, pojo);
-    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java
index 1a527d69b0..a3ca1217da 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java
@@ -42,6 +42,7 @@ public interface ObjectBookmarker {
                     "ObjectBookmarker",
                     _Lists.of(
                             new ObjectBookmarker_builtinHandlers.GuardAgainstOid(),
+                            new ObjectBookmarker_builtinHandlers.BookmarkForNonScalar(),
                             new ObjectBookmarker_builtinHandlers.BookmarkForServices(),
                             new ObjectBookmarker_builtinHandlers.BookmarkForValues(),
                             new ObjectBookmarker_builtinHandlers.BookmarkForViewModels(),
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 9130d1b88d..41a2579403 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
@@ -18,16 +18,11 @@
  */
 package org.apache.isis.core.metamodel.objectmanager.identify;
 
-import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.Oid;
-import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
-import org.apache.isis.commons.internal.base._Bytes;
-import org.apache.isis.commons.internal.base._Strings;
-import org.apache.isis.commons.internal.exceptions._Exceptions;
-import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
+import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.object.PackedManagedObject;
 import org.apache.isis.core.metamodel.objectmanager.identify.ObjectBookmarker.Handler;
@@ -37,8 +32,6 @@ import lombok.val;
 
 class ObjectBookmarker_builtinHandlers {
 
-    public static final String SERVICE_IDENTIFIER = "1";
-
     static class GuardAgainstOid implements Handler {
 
         @Override
@@ -55,6 +48,20 @@ class ObjectBookmarker_builtinHandlers {
 
     }
 
+    static class BookmarkForNonScalar implements Handler {
+
+        @Override
+        public boolean isHandling(final ManagedObject managedObject) {
+            return managedObject instanceof PackedManagedObject;
+        }
+
+        @Override
+        public Bookmark handle(final ManagedObject managedObject) {
+            return bookmarkWithRandomUUID(managedObject);
+        }
+
+    }
+
     static class BookmarkForServices implements Handler {
 
         @Override
@@ -77,7 +84,8 @@ class ObjectBookmarker_builtinHandlers {
 
         @Override
         public Bookmark handle(final ManagedObject managedObject) {
-            return managedObject.getBookmark().orElseThrow();
+            return managedObject.getBookmark()
+                    .orElseGet(()->bookmarkWithRandomUUID(managedObject)); // transient
         }
 
     }
@@ -86,33 +94,14 @@ class ObjectBookmarker_builtinHandlers {
 
         @Override
         public boolean isHandling(final ManagedObject managedObject) {
-            return managedObject.getSpecification().isValue();
+            return managedObject.getSpecialization().isValue();
         }
 
         @SneakyThrows
         @Override
         public Bookmark handle(final ManagedObject managedObject) {
-            val spec = managedObject.getSpecification();
-            val valuePojo = managedObject.getPojo();
-            if(valuePojo==null) {
-                return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), "{}");
-            }
-
-            val valueFacet = spec.valueFacet().orElse(null);
-            ValueSemanticsProvider<Object> composer = (ValueSemanticsProvider) valueFacet.selectDefaultSemantics()
-                    .orElseThrow(()->_Exceptions.illegalArgument(
-                            "Cannot create a bookmark for the value type %s, "
-                          + "as no appropriate ValueSemanticsProvider could be found.",
-                          managedObject.getSpecification().getCorrespondingClass().getName()));
-
-            val valueAsJson = composer.decompose(managedObject.getPojo())
-                    .toJson();
-
-            val identifier = _Strings.ofBytes(
-                    _Bytes.asUrlBase64.apply(valueAsJson.getBytes()),
-                    StandardCharsets.UTF_8);
-
-            return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), identifier);
+            _Assert.assertTrue(managedObject.isBookmarkSupported(), ()->"is bookmarkable");
+            return managedObject.getBookmark().orElseThrow();
         }
 
     }
@@ -121,9 +110,7 @@ class ObjectBookmarker_builtinHandlers {
 
         @Override
         public boolean isHandling(final ManagedObject managedObject) {
-            return (managedObject instanceof PackedManagedObject)
-                    ? false
-                    : managedObject.getSpecification().containsFacet(ViewModelFacet.class);
+            return managedObject.getSpecialization().isViewmodel();
         }
 
         @Override
@@ -134,8 +121,7 @@ class ObjectBookmarker_builtinHandlers {
             }
 
             val spec = managedObject.getSpecification();
-            val recreatableObjectFacet = spec.getFacet(ViewModelFacet.class);
-            return recreatableObjectFacet.serializeToBookmark(managedObject);
+            return spec.viewmodelFacetElseFail().serializeToBookmark(managedObject);
         }
 
     }
@@ -149,10 +135,16 @@ class ObjectBookmarker_builtinHandlers {
 
         @Override
         public Bookmark handle(final ManagedObject managedObject) {
-            val spec = managedObject.getSpecification();
-            val identifier = UUID.randomUUID().toString();
-            return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), identifier);
+            return bookmarkWithRandomUUID(managedObject);
         }
     }
 
+    // -- HELPER
+
+    private static Bookmark bookmarkWithRandomUUID(final ManagedObject managedObject) {
+        val uuid = UUID.randomUUID().toString();
+        System.err.printf("called bookmarkWithRandomUUID %s [%s]%n", managedObject.getSpecification(), uuid);
+        return managedObject.createBookmark(UUID.randomUUID().toString());
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java
new file mode 100644
index 0000000000..c1c33fab84
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java
@@ -0,0 +1,60 @@
+/*
+ *  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.isis.core.metamodel.spec;
+
+import java.util.Optional;
+
+import org.apache.isis.applib.id.LogicalType;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+
+import lombok.NonNull;
+
+/**
+ * Introduced as a shortcut provider.
+ */
+public interface HasObjectSpecification {
+
+    ObjectSpecification getSpecification();
+
+    // -- SHORTCUTS
+
+    default Class<?> getCorrespondingClass() {
+        return getSpecification().getCorrespondingClass();
+    }
+
+    default LogicalType getLogicalType() {
+        return getSpecification().getLogicalType();
+    }
+
+    default String getLogicalTypeName() {
+        return getSpecification().getLogicalTypeName();
+    }
+
+    /**
+     * As used for the element type of collections.
+     */
+    default Optional<ObjectSpecification> getElementSpecification() {
+        return getSpecification().getElementSpecification();
+    }
+
+    default Bookmark createBookmark(final @NonNull String urlSafeIdentifier) {
+        return Bookmark.forLogicalTypeAndIdentifier(getLogicalType(), urlSafeIdentifier);
+    }
+
+}
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 93a2ff4cb9..15f5876e44 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
@@ -41,6 +41,7 @@ import org.apache.isis.applib.ViewModel;
 import org.apache.isis.applib.exceptions.UnrecoverableException;
 import org.apache.isis.applib.services.xmlsnapshot.XmlSnapshotService.Snapshot;
 import org.apache.isis.applib.snapshot.SnapshottableWithInclusions;
+import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.codec._DocumentFactories;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
@@ -682,12 +683,12 @@ public class XmlSnapshot implements Snapshot {
                     log.debug("objectToElement(NO): {} is value", log("field", fieldName));
                 }
 
-                final ObjectSpecification fieldNos = field.getElementType();
+                final ObjectSpecification fieldSpec = field.getElementType();
                 // skip fields of type XmlValue
-                if (fieldNos == null) {
+                if (fieldSpec == null) {
                     continue eachField;
                 }
-                if (fieldNos.getFullIdentifier() != null && fieldNos.getFullIdentifier().endsWith("XmlValue")) {
+                if (fieldSpec.getFullIdentifier() != null && fieldSpec.getFullIdentifier().endsWith("XmlValue")) {
                     continue eachField;
                 }
 
@@ -701,22 +702,17 @@ public class XmlSnapshot implements Snapshot {
                 try {
                     value = valueAssociation.get(adapter, InteractionInitiatedBy.FRAMEWORK);
 
-                    final ObjectSpecification valueNos = value.getSpecification();
+                    val valueSpec = value.getSpecification();
 
                     // XML
-                    isisMetaModel.setAttributesForValue(xmlValueElement, valueNos.getShortIdentifier());
+                    isisMetaModel.setAttributesForValue(xmlValueElement, valueSpec.getShortIdentifier());
 
-                    // return encoded string, else title.
-                    String valueStr;
-                    val valueFacet = fieldNos.valueFacet().orElse(null);
-                    if (valueFacet != null) {
-                        valueStr = valueFacet.toEncodedString(Format.JSON, value.getPojo());
-                    } else {
-                        valueStr = value.getTitle();
-                    }
+                    // value as JSON
+                    @SuppressWarnings("unchecked")
+                    val valueStr = fieldSpec.valueFacetElseFail()
+                            .enstring(Format.JSON, value.getPojo());
 
-                    final boolean notEmpty = (valueStr.length() > 0);
-                    if (notEmpty) {
+                    if (_Strings.isNotEmpty(valueStr)) {
                         xmlValueElement.appendChild(getXmlDocument().createTextNode(valueStr));
                     } else {
                         isisMetaModel.setIsEmptyAttribute(xmlValueElement, true);
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java
index 694c732de7..bff7dfaa3b 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java
@@ -25,6 +25,9 @@ import java.util.Locale;
 
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 import org.apache.isis.applib.annotation.TimePrecision;
 import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException;
 import org.apache.isis.applib.locale.UserLocale;
@@ -35,9 +38,6 @@ import org.apache.isis.applib.value.semantics.ValueSemanticsProvider.Context;
 import org.apache.isis.core.metamodel.valuesemantics.temporal.LocalDateTimeValueSemantics;
 import org.apache.isis.core.metamodel.valuesemantics.temporal.legacy.JavaUtilDateValueSemantics;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
 import lombok.NonNull;
 import lombok.val;
 
@@ -146,11 +146,6 @@ extends ValueSemanticsProviderAbstractTestCase<java.util.Date> {
         return date;
     }
 
-    @Override
-    public void testValueSerializer_usingJson() {
-        // TODO fails with NPE
-    }
-
     @Override
     protected void assertValueEncodesToJsonAs(final Date a, final String json) {
         // TODO
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
index 04d5323baa..71581794b1 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
@@ -29,6 +29,14 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import org.apache.isis.applib.services.iactn.InteractionProvider;
 import org.apache.isis.applib.value.semantics.Parser;
@@ -41,16 +49,10 @@ import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer;
 import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer.Format;
-import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.facets.object.value.ValueSerializerDefault;
+import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.valuesemantics.StringValueSemantics;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
 import lombok.Getter;
 
 public abstract class ValueSemanticsProviderAbstractTestCase<T> {
@@ -135,14 +137,17 @@ public abstract class ValueSemanticsProviderAbstractTestCase<T> {
 
     }
 
-    @Test
-    public void testValueSerializer_usingJson() {
+    @ParameterizedTest
+    @EnumSource(Format.class)
+    public void testValueSerializer(final Format format) {
+        assumeValueSemanticsProviderIsSetup();
+
         final T value = getSample();
-        final String encoded = getValueSerializer().toEncodedString(Format.JSON, value);
+        final String encoded = getValueSerializer().enstring(format, value);
 
         assertValueEncodesToJsonAs(value, encoded);
 
-        T decoded = getValueSerializer().fromEncodedString(Format.JSON, encoded);
+        T decoded = getValueSerializer().destring(format, encoded);
 
         Optional.ofNullable(semantics.getOrderRelation())
             .ifPresentOrElse(rel->Assertions.assertTrue(rel.equals(value, decoded)),
@@ -153,21 +158,23 @@ public abstract class ValueSemanticsProviderAbstractTestCase<T> {
     protected abstract void assertValueEncodesToJsonAs(T a, String json);
 
 
-    @Test
-    public void testDecodeNULL() throws Exception {
+    @ParameterizedTest
+    @EnumSource(Format.class)
+    public void testDecodeNULL(final Format format) throws Exception {
         assumeValueSemanticsProviderIsSetup();
 
         final Object newValue = getValueSerializer()
-                .fromEncodedString(Format.JSON, ValueSerializerDefault.ENCODED_NULL);
+                .destring(format, ValueSerializerDefault.ENCODED_NULL);
         assertNull(newValue);
     }
 
-    @Test
-    public void testEmptyEncoding() {
+    @ParameterizedTest
+    @EnumSource(Format.class)
+    public void testEmptyEncoding(final Format format) {
         assumeValueSemanticsProviderIsSetup();
 
         assertEquals(ValueSerializerDefault.ENCODED_NULL, getValueSerializer()
-                .toEncodedString(Format.JSON, null));
+                .enstring(format, null));
     }
 
     @Test
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
index dd5a2de831..063ea58bd9 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
@@ -25,7 +25,7 @@ import org.apache.isis.core.interaction.IsisModuleCoreInteraction;
 import org.apache.isis.core.metamodel.IsisModuleCoreMetamodel;
 import org.apache.isis.core.runtime.events.MetamodelEventService;
 import org.apache.isis.core.runtime.events.TransactionEventEmitter;
-import org.apache.isis.core.runtime.idstringifier.IdStringifierService;
+import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService;
 import org.apache.isis.core.transaction.IsisModuleCoreTransaction;
 import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeIntegration;
 
@@ -42,7 +42,7 @@ import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeInte
         // @Service's
         MetamodelEventService.class,
         TransactionEventEmitter.class,
-        IdStringifierService.class,
+        IdStringifierLookupService.class,
 
         // @Configuration's
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java
similarity index 68%
rename from core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java
rename to core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java
index dcece158bf..b5101a87e6 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java
@@ -35,14 +35,13 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.ClassUtils;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.applib.annotation.ValueSemantics;
 import org.apache.isis.applib.services.bookmark.IdStringifier;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet.PrimaryKeyType;
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
 
-import lombok.NonNull;
 import lombok.val;
 
 /**
@@ -51,24 +50,19 @@ import lombok.val;
  * <p>
  * This is intended for framework use, there is little reason to call it or override it.
  *
- * @implNote yet does not support per member ValueSemantics selection;
- *      future work would look for {@link ValueSemantics} annotations on primary key members and
- *      would then honor {@link ValueSemantics#provider()} attribute,
- *      to narrow the {@link IdStringifier} search
- *
  * @since 2.0
  */
 @Service
-@Named(IsisModuleCoreRuntime.NAMESPACE + ".IdStringifierService")
+@Named(IsisModuleCoreRuntime.NAMESPACE + ".IdStringifierLookupService")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-public class IdStringifierService {
+public class IdStringifierLookupService {
 
     private final Can<IdStringifier<?>> idStringifiers;
     private final Map<Class<?>, IdStringifier<?>> stringifierByClass = new ConcurrentHashMap<>();
 
     @Inject
-    public IdStringifierService(
+    public IdStringifierLookupService(
             final List<IdStringifier<?>> idStringifiers,
             final Optional<IdStringifier<Serializable>> idStringifierForSerializableIfAny) {
         // IdStringifierForSerializable is enforced to go last, so any custom IdStringifier(s)
@@ -82,31 +76,23 @@ public class IdStringifierService {
         this.idStringifiers = Can.ofCollection(idStringifiers);
     }
 
-    public <T> String enstringPrimaryKey(final @NonNull Class<T> primaryKeyType, final @NonNull Object primaryKey) {
-        val idStringifier = lookupElseFail(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyType));
-        return idStringifier.enstring(_Casts.uncheckedCast(primaryKey));
-    }
-
-    public <T> T destringPrimaryKey(
-            final @NonNull Class<T> primaryKeyType,
-            final @NonNull Class<?> entityClass,
-            final @NonNull String stringifiedId) {
-        val idStringifier = lookupElseFail(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyType));
-        val primaryKey = idStringifier.destring(entityClass, stringifiedId);
-        return _Casts.uncheckedCast(primaryKey);
+    public <T> PrimaryKeyType<T> primaryKeyTypeFor(
+            final Class<?> entityClass, final Class<T> primaryKeyType) {
+        return PrimaryKeyType.getInstance(entityClass,
+                this::lookupIdStringifierElseFail,
+                primaryKeyType);
     }
 
-    // -- HELPER
-
-    private <T> IdStringifier<T> lookupElseFail(final Class<T> candidateValueClass) {
-        return lookup(candidateValueClass)
+    public <T> IdStringifier<T> lookupIdStringifierElseFail(final Class<T> candidateValueClass) {
+        return lookupIdStringifier(candidateValueClass)
             .orElseThrow(() -> _Exceptions.noSuchElement(
                     "Could not locate an IdStringifier to handle '%s'",
                     candidateValueClass));
     }
 
-    private <T> Optional<IdStringifier<T>> lookup(final Class<T> candidateValueClass) {
-        val idStringifier = stringifierByClass.computeIfAbsent(candidateValueClass, aClass -> {
+    public <T> Optional<IdStringifier<T>> lookupIdStringifier(final Class<T> candidateValueClass) {
+        val idStringifier = stringifierByClass.computeIfAbsent(
+                ClassUtils.resolvePrimitiveIfNecessary(candidateValueClass), aClass -> {
             for (val candidateStringifier : idStringifiers) {
                 if (candidateStringifier.getCorrespondingClass().isAssignableFrom(candidateValueClass)) {
                     return candidateStringifier;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
index 2112294f9c..a11ae430f2 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java
@@ -169,7 +169,8 @@ public class CommandDtoFactoryDefault implements CommandDtoFactory {
         dto.setUsername(userService.currentUserNameElseNobody());
         dto.setTimestamp(clockService.getClock().nowAsXmlGregorianCalendar());
 
-        final Bookmark bookmark = ManagedObjects.bookmarkElseFail(targetHead.getOwner());
+        // transient entities have no bookmark, so fallback to UUID
+        final Bookmark bookmark = ManagedObjects.bookmarkElseUUID(targetHead.getOwner());
         final OidsDto targetOids = CommandDtoUtils.targetsFor(dto);
         targetOids.getOid().add(bookmark.toOidDto());
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java
index a3201a6aac..96816d0ea5 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java
@@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.iactn.InteractionProvider;
 import org.apache.isis.applib.services.user.UserService;
@@ -36,7 +35,6 @@ import org.apache.isis.applib.util.schema.CommandDtoUtils;
 import org.apache.isis.applib.util.schema.InteractionDtoUtils;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.assertions._Assert;
-import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.execution.InteractionInternal;
 import org.apache.isis.core.metamodel.interactions.InteractionHead;
 import org.apache.isis.core.metamodel.object.ManagedObject;
@@ -86,8 +84,10 @@ public class InteractionDtoFactoryDefault implements InteractionDtoFactory {
         final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementExecutionSequence();
 
         val owner = head.getOwner();
-        final Bookmark targetBookmark = owner.getBookmark()
-                .orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner));
+
+        // transient entities have no bookmark, so fallback to UUID
+        val targetBookmark = ManagedObjects.bookmarkElseUUID(owner);
+                //.orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner));
 
         final String currentUser = userService.currentUserNameElseNobody();
 
@@ -132,8 +132,9 @@ public class InteractionDtoFactoryDefault implements InteractionDtoFactory {
         final Interaction interaction = interactionProviderProvider.get().currentInteractionElseFail();
         final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementExecutionSequence();
 
-        final Bookmark targetBookmark = targetAdapter.getBookmark()
-                .orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", targetAdapter));
+        // transient entities have no bookmark, so fallback to UUID
+        val targetBookmark = ManagedObjects.bookmarkElseUUID(targetAdapter);
+                //.orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner));
 
         final String currentUser = userService.currentUserNameElseNobody();
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java
index d398c0e8ed..cc7edb7368 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java
@@ -82,7 +82,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService {
         if(mementoAdapter==null) {
             // sonar-ignore-on (fails to detect this as null guard)
             return ManagedObjects.isSpecified(adapter)
-                    ? new ObjectMementoForEmpty(adapter.getSpecification().getLogicalType())
+                    ? new ObjectMementoForEmpty(adapter.getLogicalType())
                     : null;
             // sonar-ignore-on
         }
@@ -96,7 +96,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService {
                 .collect(Collectors.toCollection(ArrayList::new)); // ArrayList is serializable
         return ObjectMementoCollection.of(
                 listOfMementos,
-                packedAdapter.getSpecification().getLogicalType());
+                packedAdapter.getLogicalType());
     }
 
     @Override
@@ -106,7 +106,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService {
         }
         val mementoAdapter = _ObjectMemento.createOrNull(paramAdapter);
         if(mementoAdapter==null) {
-            return new ObjectMementoForEmpty(paramAdapter.getSpecification().getLogicalType());
+            return new ObjectMementoForEmpty(paramAdapter.getLogicalType());
         }
         return ObjectMementoAdapter.of(mementoAdapter);
     }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java
index 4cf1ed7816..80714a4f9b 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java
@@ -22,7 +22,6 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.function.Function;
 
 import org.springframework.lang.Nullable;
 
@@ -45,9 +44,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.util.Facets;
 
-import lombok.AccessLevel;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.NonNull;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -112,55 +109,56 @@ final class _ObjectMemento implements HasLogicalType, Serializable {
                 return memento.recreateStrategy.equals(memento, otherMemento);
             }
         },
-        /**
-         * represents a list of objects
-         */
-        VECTOR {
-
-            @Override
-            public ManagedObject asAdapter(
-                    final _ObjectMemento memento,
-                    final MetaModelContext mmc) {
-
-                // I believe this code path is no longer reachable
-                throw _Exceptions.unexpectedCodeReach();
-
-//                final Can<ManagedObject> managedObjects =
-//                        _NullSafe.stream(memento.list)
-//                        .map(Functions.toManagedObject(mmc))
-//                        .collect(Can.toCan());
+//        /**
+//         * represents a list of objects
+//         */
+//        VECTOR {
 //
-//                val commonSpec = ManagedObjects.commonSpecification(managedObjects)
-//                        .orElseGet(()->mmc.getSpecificationLoader().loadSpecification(Object.class));
+//            @Override
+//            public ManagedObject asAdapter(
+//                    final _ObjectMemento memento,
+//                    final MetaModelContext mmc) {
 //
-//                return ManagedObject.packed(commonSpec, managedObjects);
-            }
-
-            @Override
-            public Bookmark asPseudoBookmark(final _ObjectMemento memento) {
-                return Bookmark.forLogicalTypeNameAndIdentifier(
-                        memento.getLogicalTypeName(),
-                        memento.list.toString());
-            }
-
-            @Override
-            public int hashCode(final _ObjectMemento memento) {
-                return memento.list.hashCode();
-            }
-
-            @Override
-            public boolean equals(final _ObjectMemento memento, final Object other) {
-                if (!(other instanceof _ObjectMemento)) {
-                    return false;
-                }
-                final _ObjectMemento otherMemento = (_ObjectMemento) other;
-                if(otherMemento.cardinality != VECTOR) {
-                    return false;
-                }
-                return memento.list.equals(otherMemento.list);
-            }
-
-        };
+//                // I believe this code path is no longer reachable
+//                throw _Exceptions.unexpectedCodeReach();
+//
+////                final Can<ManagedObject> managedObjects =
+////                        _NullSafe.stream(memento.list)
+////                        .map(Functions.toManagedObject(mmc))
+////                        .collect(Can.toCan());
+////
+////                val commonSpec = ManagedObjects.commonSpecification(managedObjects)
+////                        .orElseGet(()->mmc.getSpecificationLoader().loadSpecification(Object.class));
+////
+////                return ManagedObject.packed(commonSpec, managedObjects);
+//            }
+//
+//            @Override
+//            public Bookmark asPseudoBookmark(final _ObjectMemento memento) {
+//                return Bookmark.forLogicalTypeNameAndIdentifier(
+//                        memento.getLogicalTypeName(),
+//                        memento.list.toString());
+//            }
+//
+//            @Override
+//            public int hashCode(final _ObjectMemento memento) {
+//                return memento.list.hashCode();
+//            }
+//
+//            @Override
+//            public boolean equals(final _ObjectMemento memento, final Object other) {
+//                if (!(other instanceof _ObjectMemento)) {
+//                    return false;
+//                }
+//                final _ObjectMemento otherMemento = (_ObjectMemento) other;
+//                if(otherMemento.cardinality != VECTOR) {
+//                    return false;
+//                }
+//                return memento.list.equals(otherMemento.list);
+//            }
+//
+//        }
+        ;
 
         void ensure(final Cardinality sort) {
             if(this == sort) {
@@ -192,14 +190,17 @@ final class _ObjectMemento implements HasLogicalType, Serializable {
                     final _ObjectMemento memento,
                     final MetaModelContext mmc) {
 
-                val valueSerializer = mmc.getSpecificationLoader()
+                val valueSpec = mmc.getSpecificationLoader()
                         .specForLogicalType(memento.logicalType)
-                        .flatMap(spec->Facets.valueSerializer(spec, spec.getCorrespondingClass()))
                         .orElseThrow(()->_Exceptions.unrecoverable(
-                                "logical type %s is expected to have a ValueFacet", memento.logicalType));
+                                "logical type %s is not recognized", memento.logicalType));
 
-                return mmc.getObjectManager().adapt(
-                        valueSerializer.fromEncodedString(Format.JSON, memento.encodableValue));
+                val valueSerializer = Facets.valueSerializer(valueSpec, valueSpec.getCorrespondingClass())
+                        .orElseThrow(()->_Exceptions.unrecoverable(
+                                "logical type %s is expected to have a value semantics", memento.logicalType));
+
+                return ManagedObject.value(valueSpec,
+                        valueSerializer.destring(Format.URL_SAFE, memento.encodableValue));
             }
 
             @Override
@@ -413,9 +414,11 @@ final class _ObjectMemento implements HasLogicalType, Serializable {
             final ArrayList<_ObjectMemento> list,
             final LogicalType logicalType) {
 
-        this.cardinality = Cardinality.VECTOR;
-        this.list = list;
-        this.logicalType = logicalType;
+        throw _Exceptions.unexpectedCodeReach();
+
+        //this.cardinality = Cardinality.VECTOR;
+//        this.list = list;
+//        this.logicalType = logicalType;
     }
 
     private _ObjectMemento(final Bookmark bookmark, final SpecificationLoader specificationLoader) {
@@ -482,7 +485,7 @@ final class _ObjectMemento implements HasLogicalType, Serializable {
                 .orElse(null);
         val isEncodable = valueSerializer != null;
         if (isEncodable) {
-            encodableValue = valueSerializer.toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo()));
+            encodableValue = valueSerializer.enstring(Format.URL_SAFE, _Casts.uncheckedCast(adapter.getPojo()));
             recreateStrategy = RecreateStrategy.VALUE;
             return;
         }
@@ -578,29 +581,6 @@ final class _ObjectMemento implements HasLogicalType, Serializable {
         return cardinality.equals(this, obj);
     }
 
-    // -- FUNCTIONS
-
-    @NoArgsConstructor(access = AccessLevel.PRIVATE)
-    private static final class Functions {
-
-        private static Function<_ObjectMemento, ManagedObject> toManagedObject(
-                final MetaModelContext mmc) {
-
-            return memento->{
-                if(memento == null) {
-                    return ManagedObject.unspecified();
-                }
-                val objectAdapter = memento
-                        .reconstructObject(mmc);
-                if(objectAdapter == null) {
-                    return ManagedObject.unspecified();
-                }
-                return objectAdapter;
-            };
-        }
-
-    }
-
     private void ensureScalar() {
         getCardinality().ensure(Cardinality.SCALAR);
     }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java
index 0e2fa29a70..25a86cc6e3 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java
@@ -67,7 +67,8 @@ public class DnEntityStateProvider implements JdoFacetContext {
             return EntityState.NOT_PERSISTABLE;
         }
 
-        if (pojo!=null && pojo instanceof Persistable) {
+        if (pojo!=null
+                && pojo instanceof Persistable) {
             val persistable = (Persistable) pojo;
             val isDeleted = persistable.dnIsDeleted();
             if(isDeleted) {
@@ -75,8 +76,11 @@ public class DnEntityStateProvider implements JdoFacetContext {
             }
             val isPersistent = persistable.dnIsPersistent();
             if(isPersistent) {
-                return persistable.dnGetObjectId()!=null
-                        ? EntityState.PERSISTABLE_ATTACHED
+                val oid = persistable.dnGetObjectId();
+                return oid!=null
+                        ? persistable.dnGetStateManager().isNew(persistable)
+                                ? EntityState.PERSISTABLE_NEW
+                                : EntityState.PERSISTABLE_ATTACHED
                         : EntityState.PERSISTABLE_NEW;
             }
             return EntityState.PERSISTABLE_DETACHED;
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
index 6865d76cd1..b86cbe86f1 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
@@ -55,14 +55,17 @@ import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet;
 import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
-import org.apache.isis.core.runtime.idstringifier.IdStringifierService;
+import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService;
 import org.apache.isis.persistence.jdo.datanucleus.entities.DnEntityStateProvider;
 import org.apache.isis.persistence.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacetFactory;
 import org.apache.isis.persistence.jdo.provider.entities.JdoFacetContext;
 import org.apache.isis.persistence.jdo.spring.integration.TransactionAwarePersistenceManagerFactoryProxy;
 
+import lombok.AccessLevel;
+import lombok.Getter;
 import lombok.NonNull;
 import lombok.val;
+import lombok.experimental.Accessors;
 import lombok.extern.log4j.Log4j2;
 
 /**
@@ -80,10 +83,18 @@ implements EntityFacet {
     @Inject private ObjectManager objectManager;
     @Inject private ExceptionRecognizerService exceptionRecognizerService;
     @Inject private JdoFacetContext jdoFacetContext;
-    @Inject private IdStringifierService idStringifierService;
+    @Inject private ObjectLifecyclePublisher objectLifecyclePublisher;
+
+    @Getter(value = AccessLevel.PROTECTED) @Accessors(fluent = true)
+    @Inject private IdStringifierLookupService idStringifierLookupService;
 
     private final Class<?> entityClass;
 
+    // lazily looks up the primaryKeyTypeFor (needs a PersistenceManager)
+    @Getter(lazy=true, value = AccessLevel.PROTECTED) @Accessors(fluent = true)
+    private final PrimaryKeyType<?> primaryKeyTypeForDecoding = idStringifierLookupService()
+            .primaryKeyTypeFor(entityClass, primaryKeyTypeFor(entityClass));
+
     public JdoEntityFacet(
             final FacetHolder holder, final Class<?> entityClass) {
         super(EntityFacet.class, holder);
@@ -95,6 +106,19 @@ implements EntityFacet {
         return PersistenceStack.JDO;
     }
 
+    /* OPTIMIZATION
+     * even though the IdStringifierLookupService already holds a lookup map,
+     * for performance reasons,
+     * we define another one local to the context of the associated entity class */
+    private final Map<Class<?>, PrimaryKeyType<?>> primaryKeyTypesForEncoding = new ConcurrentHashMap<>();
+    private final PrimaryKeyType<?> primaryKeyTypeForEncoding(final @NonNull Object oid) {
+        val actualPrimaryKeyClass = oid.getClass();
+        val primaryKeyType = primaryKeyTypesForEncoding.computeIfAbsent(actualPrimaryKeyClass, key->
+            idStringifierLookupService()
+                .primaryKeyTypeFor(entityClass, actualPrimaryKeyClass));
+        return primaryKeyType;
+    }
+
     @Override
     public Optional<String> identifierFor(final Object pojo) {
 
@@ -105,9 +129,10 @@ implements EntityFacet {
         val pm = getPersistenceManager();
         var primaryKeyIfAny = pm.getObjectId(pojo);
 
-        return Optional.ofNullable(primaryKeyIfAny)
+        val idIfAny = Optional.ofNullable(primaryKeyIfAny)
                 .map(primaryKey->
-                    idStringifierService.enstringPrimaryKey(primaryKey.getClass(), primaryKey));
+                    primaryKeyTypeForEncoding(primaryKey).enstringWithCast(primaryKey));
+        return idIfAny;
     }
 
     @Override
@@ -119,8 +144,7 @@ implements EntityFacet {
         try {
 
             val persistenceManager = getPersistenceManager();
-            val primaryKey = idStringifierService
-                    .destringPrimaryKey(primaryKeyTypeFor(entityClass), entityClass, bookmark.getIdentifier());
+            val primaryKey = primaryKeyTypeForDecoding().destring(bookmark.getIdentifier());
 
             val fetchPlan = persistenceManager.getFetchPlan();
             fetchPlan.addGroup(FetchGroup.DEFAULT);
@@ -364,13 +388,11 @@ implements EntityFacet {
 
     private Can<ManagedObject> fetchWithinTransaction(final Supplier<List<?>> fetcher) {
 
-        val objectLifecyclePublisher = getFacetHolder().getServiceRegistry()
-                .lookupServiceElseFail(ObjectLifecyclePublisher.class);
-
         return getTransactionalProcessor().callWithinCurrentTransactionElseCreateNew(
                 ()->_NullSafe.stream(fetcher.get())
                     .map(fetchedObject->adopt(objectLifecyclePublisher, fetchedObject))
                     .collect(Can.toCan()))
+                .ifFailureFail()
                 .getValue().orElseThrow();
     }
 
@@ -393,5 +415,4 @@ implements EntityFacet {
         }
     }
 
-
 }
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java
index 7c764ec931..1eb57800fa 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java
@@ -21,6 +21,7 @@ package org.apache.isis.persistence.jpa.integration.entity;
 import java.lang.reflect.Method;
 import java.util.Optional;
 
+import javax.inject.Inject;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceException;
 import javax.persistence.PersistenceUnitUtil;
@@ -34,7 +35,6 @@ import org.apache.isis.applib.query.AllInstancesQuery;
 import org.apache.isis.applib.query.NamedQuery;
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.applib.services.repository.EntityState;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Casts;
@@ -45,7 +45,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet;
 import org.apache.isis.core.metamodel.object.ManagedObject;
-import org.apache.isis.core.runtime.idstringifier.IdStringifierService;
+import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService;
 
 import lombok.NonNull;
 import lombok.val;
@@ -56,19 +56,22 @@ public class JpaEntityFacet
         extends FacetAbstract
         implements EntityFacet {
 
+    // self managed injections via constructor
+    @Inject private JpaContext jpaContext;
+    @Inject private IdStringifierLookupService idStringifierLookupService;
+
     private final Class<?> entityClass;
-    private final ServiceRegistry serviceRegistry;
-    private final IdStringifierService idStringifierService;
+    private PrimaryKeyType<?> primaryKeyType;
 
     protected JpaEntityFacet(
             final FacetHolder holder,
-            final Class<?> entityClass,
-            final @NonNull ServiceRegistry serviceRegistry) {
-
+            final Class<?> entityClass) {
         super(EntityFacet.class, holder, Precedence.HIGH);
+        getServiceInjector().injectServicesInto(this);
+
         this.entityClass = entityClass;
-        this.serviceRegistry = serviceRegistry;
-        this.idStringifierService = serviceRegistry.lookupServiceElseFail(IdStringifierService.class);
+        this.primaryKeyType = idStringifierLookupService
+                .primaryKeyTypeFor(entityClass, getPrimaryKeyType());
     }
 
     // -- ENTITY FACET
@@ -91,7 +94,7 @@ public class JpaEntityFacet
 
         return Optional.ofNullable(primaryKeyIfAny)
                 .map(primaryKey->
-                    idStringifierService.enstringPrimaryKey(getPrimaryKeyType(), primaryKey));
+                    primaryKeyType.enstringWithCast(primaryKey));
     }
 
     @Override
@@ -99,8 +102,7 @@ public class JpaEntityFacet
 
         log.debug("fetchEntity; bookmark={}", bookmark);
 
-        val primaryKey = idStringifierService
-                .destringPrimaryKey(getPrimaryKeyType(), entityClass, bookmark.getIdentifier());
+        val primaryKey = primaryKeyType.destring(bookmark.getIdentifier());
 
         val entityManager = getEntityManager();
         val entityPojo = entityManager.find(entityClass, primaryKey);
@@ -311,12 +313,8 @@ public class JpaEntityFacet
 
     // -- DEPENDENCIES
 
-    protected JpaContext getJpaContext() {
-        return serviceRegistry.lookupServiceElseFail(JpaContext.class);
-    }
-
     protected EntityManager getEntityManager() {
-        return getJpaContext().getEntityManagerByManagedType(entityClass);
+        return jpaContext.getEntityManagerByManagedType(entityClass);
     }
 
     protected PersistenceUnitUtil getPersistenceUnitUtil(final EntityManager entityManager) {
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
index ca1ed4ed6d..cf482fab56 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java
@@ -49,7 +49,7 @@ extends FacetFactoryAbstract {
         }
 
         addFacet(
-                new JpaEntityFacet(facetHolder, cls, getServiceRegistry()));
+                new JpaEntityFacet(facetHolder, cls));
     }
 
 }
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
index 6608afeb62..72d006b5d1 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
@@ -127,7 +127,7 @@ public class JsonValueEncoderServiceDefault implements JsonValueEncoderService {
             final ValueSerializer<?> valueSerializer) {
         if (valueRepr.isString()) {
             val recoveredValue = Try.call(()->
-                    valueSerializer.fromEncodedString(Format.JSON, valueRepr.asString()))
+                    valueSerializer.destring(Format.JSON, valueRepr.asString()))
                     .mapFailure(ex->_Exceptions
                             .illegalArgument(ex, "Unable to parse value %s as String", valueRepr))
                     .ifFailureFail()
@@ -235,7 +235,7 @@ public class JsonValueEncoderServiceDefault implements JsonValueEncoderService {
 
         // else
         return Facets.valueSerializerElseFail(objectSpec, cls)
-                .toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo()));
+                .enstring(Format.JSON, _Casts.uncheckedCast(adapter.getPojo()));
     }
 
     /**
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java
index 44075d5f0b..3fe8b00bfe 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java
@@ -33,10 +33,8 @@ import org.springframework.lang.Nullable;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.commons.collections.Can;
-import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer.Format;
 import org.apache.isis.core.metamodel.object.ManagedObject;
 import org.apache.isis.core.metamodel.object.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -210,13 +208,6 @@ public class PageParameterUtils {
         if(adapter == null) {
             return NULL_ARG;
         }
-
-        final ObjectSpecification objSpec = adapter.getSpecification();
-        if(objSpec.isValue()) {
-            return Facets.valueSerializerElseFail(objSpec, objSpec.getCorrespondingClass())
-            .toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo()));
-        }
-
         return ManagedObjects.stringify(adapter).orElse(null);
     }
 
@@ -227,13 +218,6 @@ public class PageParameterUtils {
         if(NULL_ARG.equals(encoded)) {
             return null;
         }
-
-        if(objSpec.isValue()) {
-            return ManagedObject.value(objSpec,
-                    Facets.valueSerializerElseFail(objSpec, objSpec.getCorrespondingClass())
-                        .fromEncodedString(Format.JSON, encoded));
-        }
-
         try {
             return Bookmark.parseUrlEncoded(encoded)
                     .flatMap(mmc::loadObject)