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/03 17:08:39 UTC

[isis] branch master updated: ISIS-3202: JDO injection point resolving optimizations

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 f2882a8d37 ISIS-3202: JDO injection point resolving optimizations
f2882a8d37 is described below

commit f2882a8d37a694b3eed07ab0415f6313b78bf620
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sat Sep 3 19:08:32 2022 +0200

    ISIS-3202: JDO injection point resolving optimizations
---
 .../facets/object/entity/EntityFacet.java          | 12 +++-
 .../isis/core/metamodel/object/ManagedObject.java  | 76 +++++++++++-----------
 .../object/_ManagedObjectEntityHybrid.java         |  8 +++
 .../metamodel/object/_ManagedObjectSpecified.java  | 12 +++-
 .../object/_ManagedObjectSpecifiedLegacy.java      | 52 ---------------
 .../changetracking/JdoLifecycleListener.java       | 11 ++--
 .../jdo/datanucleus/changetracking/_Utils.java     | 69 +++++---------------
 .../entities/DnObjectProviderForIsis.java          | 56 +++++++++++-----
 .../metamodel/facets/entity/JdoEntityFacet.java    | 36 ++++------
 .../persistence/jpa/eclipselink/inject/_Util.java  | 23 +++----
 .../viewer/resources/HomePageReprRenderer.java     |  2 +-
 11 files changed, 153 insertions(+), 204 deletions(-)

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 b814ad62a0..64c62929da 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
@@ -92,6 +92,16 @@ public interface EntityFacet extends Facet {
         return (ObjectSpecification)getFacetHolder();
     }
 
+    /**
+     * Introduced purely for optimization purposes.
+     * @implNote if possible memoizes the fact as to whether
+     *      services were already injected into given pojo,
+     *      and if so allows to skip any consecutive injection attempts
+     */
+    default boolean isInjectionPointsResolved(final @Nullable Object pojo) {
+        return pojo==null;
+    }
+
     /**
      * Optionally the stringified OID,
      * based on whether the entity has one associated.
@@ -158,6 +168,4 @@ public interface EntityFacet extends Facet {
         return new _EntityFacetForTesting(persistenceStandard, facetHolder);
     }
 
-
-
 }
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 ef2646d3af..6baff36a98 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
@@ -69,7 +69,8 @@ extends
          * </ul>
          * @implNote realized by a singleton (static) {@link ManagedObject} instance;
          */
-        UNSPECIFIED(TypePolicy.NO_TYPE, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO),
+        UNSPECIFIED(TypePolicy.NO_TYPE, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO,
+                InjectionPolicy.NEVER_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -78,7 +79,8 @@ extends
          * <li>Pojo (null, immutable)</li>
          * </ul>
          */
-        EMPTY(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO),
+        EMPTY(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.NO_POJO,
+                InjectionPolicy.NEVER_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -87,7 +89,8 @@ extends
          * <li>Pojo (immutable)</li>
          * </ul>
          */
-        VALUE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE),
+        VALUE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE,
+                InjectionPolicy.NEVER_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -96,7 +99,8 @@ extends
          * <li>Pojo (immutable)</li>
          * </ul>
          */
-        SERVICE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE),
+        SERVICE(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.IMMUTABLE,
+                InjectionPolicy.NEVER_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -105,7 +109,8 @@ extends
          * <li>Pojo (mutable, but immutable obj. ref.)</li>
          * </ul>
          */
-        VIEWMODEL(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.REFRESHABLE, PojoPolicy.STATEFUL),
+        VIEWMODEL(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.REFRESHABLE, PojoPolicy.STATEFUL,
+                InjectionPolicy.ALWAYS_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -114,7 +119,8 @@ extends
          * <li>Pojo (refetchable)</li>
          * </ul>
          */
-        ENTITY(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.REFETCHABLE),
+        ENTITY(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.IMMUTABLE, PojoPolicy.REFETCHABLE,
+                InjectionPolicy.ALWAYS_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -123,7 +129,8 @@ extends
          * <li>Pojo (allowed stateful, immutable obj. ref)</li>
          * </ul>
          */
-        MIXIN(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL),
+        MIXIN(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL,
+                InjectionPolicy.ALWAYS_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -132,7 +139,8 @@ extends
          * <li>Pojo (allowed stateful, immutable obj. ref)</li>
          * </ul>
          */
-        OTHER(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL),
+        OTHER(TypePolicy.EXACT_TYPE_REQUIRED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.STATEFUL,
+                InjectionPolicy.NEVER_INJECT),
 
         /**
          * <h1>Contract</h1><ul>
@@ -141,7 +149,8 @@ extends
          * <li>Pojo (unmod. Collection of pojos)</li>
          * </ul>
          */
-        PACKED(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.PACKED);
+        PACKED(TypePolicy.ABSTRACT_TYPE_ALLOWED, BookmarkPolicy.NO_BOOKMARK, PojoPolicy.PACKED,
+                InjectionPolicy.NEVER_INJECT);
 
         static enum TypePolicy {
             /** has no type information */
@@ -200,10 +209,22 @@ extends
              * supports unpacking into a {@link Can} of {@link ManagedObject}s;*/
             public boolean isPacked() { return this == PACKED; }
         }
+        static enum InjectionPolicy {
+            /** don't inject services into accompanied pojo */
+            NEVER_INJECT,
+            /** always inject services into accompanied pojo */
+            ALWAYS_INJECT;
+            ////
+            /** don't inject services into accompanied pojo */
+            public boolean isNeverInject() { return this == NEVER_INJECT; }
+            /** always inject services into accompanied pojo */
+            public boolean isAlwaysInject() { return this == ALWAYS_INJECT; }
+        }
 
         private final TypePolicy typePolicy;
         private final BookmarkPolicy bookmarkPolicy;
         private final PojoPolicy pojoPolicy;
+        private final InjectionPolicy injectionPolicy;
 
         /**
          * UNSPECIFIED
@@ -273,37 +294,18 @@ extends
         public static Specialization inferFrom(
                 final @Nullable ObjectSpecification spec,
                 final @Nullable Object pojo) {
-            if(spec==null) {
-                return UNSPECIFIED;
-            }
-            if(spec.isNonScalar()) {
-                return PACKED;
-            }
-            if(pojo==null) {
-                return EMPTY;
-            }
-            if(spec.isValue()) {
-                return VALUE;
-            }
-            if(spec.isInjectable()) {
-                return SERVICE;
-            }
-            if(spec.isViewModel()) {
-                return VIEWMODEL;
-            }
-            if(spec.isEntity()) {
-                return ENTITY;
-            }
-            if(spec.isMixin()) {
-                return MIXIN;
-            }
-            if(!spec.isAbstract()) {
-                return OTHER;
-            }
+            if(spec==null) { return UNSPECIFIED; }
+            if(spec.isNonScalar()) { return PACKED; }
+            if(pojo==null) { return EMPTY; }
+            if(spec.isValue()) { return VALUE; }
+            if(spec.isInjectable()) { return SERVICE; }
+            if(spec.isViewModel()) { return VIEWMODEL; }
+            if(spec.isEntity()) { return ENTITY; }
+            if(spec.isMixin()) { return MIXIN; }
+            if(!spec.isAbstract()) { return OTHER; }
             log.warn("failed specialization attempt for {}", spec);
             return UNSPECIFIED;
         }
-
     }
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectEntityHybrid.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectEntityHybrid.java
index 91df684f61..99f4bf9ea1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectEntityHybrid.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectEntityHybrid.java
@@ -125,6 +125,14 @@ implements Refetchable {
             .fold(Refetchable::peekAtPojo, Refetchable::peekAtPojo);
     }
 
+    @Override
+    protected boolean isInjectionPointsResolved() {
+        // overriding the default for optimization, let the EntityFacet handle injection
+        // as a side-effect potentially injects if required
+        return getSpecification().entityFacetElseFail()
+                .isInjectionPointsResolved(peekAtPojo());
+    }
+
     // -- HELPER
 
     private void triggerReassessment() {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecified.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecified.java
index 76a9f9efe7..d4b1748a4b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecified.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecified.java
@@ -67,12 +67,20 @@ implements ManagedObject {
         if(specialization.getTypePolicy().isExactTypeRequired()) {
             MmAssertionUtil.assertExactType(specification, pojo);
         }
-        if(getSpecification().isEntityOrViewModel()) {
-            getServiceInjector().injectServicesInto(pojo); // might be redundant
+        if(getSpecialization().getInjectionPolicy().isAlwaysInject()) {
+            if(!isInjectionPointsResolved()) {
+                getServiceInjector().injectServicesInto(pojo); // might be redundant
+            }
         }
         return pojo;
     }
 
+    /**
+     * override if there is optimization available
+     * @apiNote must only be called by {@link #assertCompliance(Object)}
+     */
+    protected boolean isInjectionPointsResolved() { return false; }
+
     @Override
     public String getTitle() {
         return _InternalTitleUtil.titleString(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecifiedLegacy.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecifiedLegacy.java
deleted file mode 100644
index 05e17541ca..0000000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectSpecifiedLegacy.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- *  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.object;
-
-import java.util.function.Supplier;
-
-import org.apache.isis.core.metamodel.context.MetaModelContext;
-
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.experimental.Accessors;
-
-@RequiredArgsConstructor
-abstract class _ManagedObjectSpecifiedLegacy
-implements ManagedObject {
-
-    @Getter(onMethod_ = {@Override}) @Accessors(makeFinal = true)
-    private final Specialization specialization;
-
-    @Override
-    public final MetaModelContext getMetaModelContext() {
-        return getSpecification().getMetaModelContext();
-    }
-
-    @Override
-    public final Supplier<ManagedObject> asSupplier() {
-        return ()->this;
-    }
-
-    @Override
-    public final <T> T assertCompliance(final @NonNull T pojo) {
-        return pojo; // legacy implementation - don't check
-    }
-
-}
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
index 056184fc1f..1d92379e16 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
@@ -33,7 +33,6 @@ import org.datanucleus.enhancement.Persistable;
 
 import org.apache.isis.commons.functional.Either;
 import org.apache.isis.commons.internal.assertions._Assert;
-import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
 import org.apache.isis.core.metamodel.object.ManagedObject;
@@ -91,9 +90,7 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
         log.debug("postLoad {}", ()->_Utils.debug(event));
         final Persistable pojo = _Utils.persistableFor(event);
         val entity = adaptEntity(pojo);
-
         objectLifecyclePublisher.onPostLoad(entity);
-
     }
 
     @Override
@@ -153,10 +150,10 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
 
         // [ISIS-3126] pre-dirty nested loop prevention,
         // assuming we can cast the DN StateManager to the custom one as provided by the framework
-        _Casts.castTo(DnObjectProviderForIsis.class, pojo.dnGetStateManager())
-        .ifPresentOrElse(stateManager->
-                stateManager.acquirePreDirtyPropagationLock(pojo.dnGetObjectId())
-                .ifPresent(lock->lock.releaseAfter(doPreDirty)),
+        DnObjectProviderForIsis.extractFrom(pojo).ifPresentOrElse(
+                stateManager->
+                    stateManager.acquirePreDirtyPropagationLock(pojo.dnGetObjectId())
+                    .ifPresent(lock->lock.releaseAfter(doPreDirty)),
                 doPreDirty);
     }
 
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/_Utils.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/_Utils.java
index cd46eb93c0..2d34d36bc5 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/_Utils.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/_Utils.java
@@ -23,12 +23,11 @@ import javax.jdo.ObjectState;
 import javax.jdo.listener.InstanceLifecycleEvent;
 
 import org.datanucleus.enhancement.Persistable;
-import org.springframework.lang.Nullable;
 
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.object.ManagedObject;
-import org.apache.isis.core.metamodel.object.ManagedObjects;
+import org.apache.isis.persistence.jdo.datanucleus.entities.DnObjectProviderForIsis;
 
 import lombok.NonNull;
 import lombok.val;
@@ -46,19 +45,11 @@ final class _Utils {
             final @NonNull InstanceLifecycleEvent event) {
         final Persistable pojo = _Utils.persistableFor(event);
         if(pojo!=null) {
-            mmc.getServiceInjector().injectServicesInto(pojo);
-        }
-    }
-
-    String debug(final InstanceLifecycleEvent event) {
-        // try to be side-effect free here ...
-        final Persistable pojo = _Utils.persistableFor(event);
-        ObjectState state = JDOHelper.getObjectState(pojo);
-        //if(state == ObjectState.PERSISTENT_CLEAN) {
-            //return String.format("entity: %s", pojo);
-        //} else {
-            return String.format("entity: %s (%s)", pojo.getClass().getSimpleName(), state);
-        //}
+            DnObjectProviderForIsis.extractFrom(pojo)
+            .ifPresentOrElse(
+                    DnObjectProviderForIsis::injectServicesIfNotAlready,
+                    ()->mmc.getServiceInjector().injectServicesInto(pojo));
+            }
     }
 
     ManagedObject adaptEntity(
@@ -71,45 +62,15 @@ final class _Utils {
         return entity;
     }
 
-    ManagedObject adaptNullableEntity(
-            final @NonNull MetaModelContext mmc,
-            final @Nullable Object entityPojo) {
-
-        return entityPojo == null
-                ? ManagedObject.unspecified()
-                : adaptEntity(mmc, entityPojo);
-    }
-
-    ManagedObject adaptNullableAndInjectServices(
-            final @NonNull MetaModelContext mmc,
-            final @Nullable Object entityPojo) {
-
-        return entityPojo == null
-                ? ManagedObject.unspecified()
-                : adaptEntityAndInjectServices(mmc, entityPojo);
-    }
-
-    ManagedObject adaptEntityAndInjectServices(
-            final @NonNull MetaModelContext mmc,
-            final @NonNull Object entityPojo) {
-        return injectServices(mmc, adaptEntity(mmc, entityPojo));
-    }
-
-
-    private static ManagedObject injectServices(
-            final @NonNull MetaModelContext mmc,
-            final @NonNull ManagedObject adapter) {
-
-        if(ManagedObjects.isNullOrUnspecifiedOrEmpty(adapter)) {
-            return adapter;
-        }
-
-        if(adapter.getSpecification().isValue()) {
-            return adapter; // guard against value objects
-        }
-        mmc.getServiceInjector().injectServicesInto(adapter.getPojo());
-        return adapter;
+    String debug(final InstanceLifecycleEvent event) {
+        // try to be side-effect free here ...
+        final Persistable pojo = _Utils.persistableFor(event);
+        ObjectState state = JDOHelper.getObjectState(pojo);
+        //if(state == ObjectState.PERSISTENT_CLEAN) {
+            //return String.format("entity: %s", pojo);
+        //} else {
+            return String.format("entity: %s (%s)", pojo.getClass().getSimpleName(), state);
+        //}
     }
 
-
 }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnObjectProviderForIsis.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnObjectProviderForIsis.java
index 1abbcb9c8f..53db0aac31 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnObjectProviderForIsis.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnObjectProviderForIsis.java
@@ -29,8 +29,10 @@ import org.datanucleus.enhancement.Persistable;
 import org.datanucleus.metadata.AbstractClassMetaData;
 import org.datanucleus.state.ReferentialStateManagerImpl;
 import org.datanucleus.store.FieldValues;
+import org.springframework.lang.Nullable;
 
 import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.persistence.jdo.spring.integration.TransactionAwarePersistenceManagerFactoryProxy;
@@ -54,65 +56,66 @@ extends ReferentialStateManagerImpl {
         this.serviceInjector = extractServiceInjectorFrom(ec).orElse(null);
     }
 
+    @SuppressWarnings("rawtypes")
     @Override
     public void initialiseForHollow(final Object id, final FieldValues fv, final Class pcClass) {
         super.initialiseForHollow(id, fv, pcClass);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
-    @SuppressWarnings("deprecation")
+    @SuppressWarnings({ "deprecation", "rawtypes" })
     @Override
     public void initialiseForHollowAppId(final FieldValues fv, final Class pcClass) {
         super.initialiseForHollowAppId(fv, pcClass);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForHollowPreConstructed(final Object id, final Persistable pc) {
         super.initialiseForHollowPreConstructed(id, pc);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForPersistentClean(final Object id, final Persistable pc) {
         super.initialiseForPersistentClean(id, pc);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForEmbedded(final Persistable pc, final boolean copyPc) {
         super.initialiseForEmbedded(pc, copyPc);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForPersistentNew(final Persistable pc, final FieldValues preInsertChanges) {
         super.initialiseForPersistentNew(pc, preInsertChanges);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForTransactionalTransient(final Persistable pc) {
         super.initialiseForTransactionalTransient(pc);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForDetached(final Persistable pc, final Object id, final Object version) {
         super.initialiseForDetached(pc, id, version);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForPNewToBeDeleted(final Persistable pc) {
         super.initialiseForPNewToBeDeleted(pc);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     @Override
     public void initialiseForCachedPC(final CachedPC cachedPC, final Object id) {
         super.initialiseForCachedPC(cachedPC, id);
-        injectServices(myPC);
+        injectServicesIfNotAlready();
     }
 
     // -- HELPER
@@ -143,17 +146,29 @@ extends ReferentialStateManagerImpl {
         return Optional.of(serviceInjector);
     }
 
-    private void injectServices(final Persistable entity) {
-        if(entity==null) {
-            return;
+    private boolean injectionPointsResolved = false;
+
+    /**
+     * Returns whether injection points are resolved for myPC.
+     */
+    public boolean injectServicesIfNotAlready() {
+        if(myPC==null) {
+            this.injectionPointsResolved = false; // reset
+            return true;
+        }
+        if(injectionPointsResolved) {
+            //XXX would be nice count as a metric
+            return true;
         }
         if(serviceInjector!=null) {
-            serviceInjector.injectServicesInto(entity);
+            serviceInjector.injectServicesInto(myPC);
+            this.injectionPointsResolved = true;
         } else {
             log.warn("cannot inject services into entity of type {}, "
                     + "as there is no ServiceInjector available",
-                    entity.getClass());
+                    myPC.getClass());
         }
+        return injectionPointsResolved;
     }
 
     // -- [ISIS-3126] PRE-DIRTY NESTED LOOP PREVENTION
@@ -175,6 +190,7 @@ extends ReferentialStateManagerImpl {
     private final Map<Object, PreDirtyPropagationLock> preDirtyPropagationLocks =
             _Maps.newHashMap();
 
+    //TODO there is probably only ever one id per instance: verify an simplify
     private final PreDirtyPropagationLock createPreDirtyPropagationLock(final Object id) {
         return ()->preDirtyPropagationLocks.remove(id);
     }
@@ -202,6 +218,14 @@ extends ReferentialStateManagerImpl {
         return lockIfGranted;
     }
 
+    // -- UTILITY
+
+    public static Optional<DnObjectProviderForIsis> extractFrom(final @Nullable Persistable pojo) {
+        return pojo!=null
+                ? _Casts.castTo(DnObjectProviderForIsis.class, pojo.dnGetStateManager())
+                : Optional.empty();
+    }
+
     // --
 
     /*
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 2e6ac99997..a103cc544e 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
@@ -57,6 +57,7 @@ import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService;
 import org.apache.isis.persistence.jdo.datanucleus.entities.DnEntityStateProvider;
+import org.apache.isis.persistence.jdo.datanucleus.entities.DnObjectProviderForIsis;
 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;
@@ -78,6 +79,7 @@ public class JdoEntityFacet
 extends FacetAbstract
 implements EntityFacet {
 
+    // self managed injections via getPersistenceManager or getTransactionalProcessor
     @Inject private TransactionAwarePersistenceManagerFactoryProxy pmf;
     @Inject private TransactionService txService;
     @Inject private ObjectManager objectManager;
@@ -119,6 +121,16 @@ implements EntityFacet {
         return primaryKeyType;
     }
 
+    @Override
+    public boolean isInjectionPointsResolved(final Object pojo) {
+        if(pojo instanceof Persistable) {
+            DnObjectProviderForIsis.extractFrom((Persistable) pojo)
+            .map(DnObjectProviderForIsis::injectServicesIfNotAlready)
+            .orElse(false);
+        }
+        return pojo==null;
+    }
+
     @Override
     public Optional<String> identifierFor(final Object pojo) {
 
@@ -292,8 +304,6 @@ implements EntityFacet {
         getTransactionalProcessor()
         .runWithinCurrentTransactionElseCreateNew(()->pm.makePersistent(pojo))
         .ifFailureFail();
-
-        //TODO integrate with entity change tracking
     }
 
     @Override
@@ -314,8 +324,6 @@ implements EntityFacet {
         getTransactionalProcessor()
         .runWithinCurrentTransactionElseCreateNew(()->pm.deletePersistent(pojo))
         .ifFailureFail();
-
-        //TODO integrate with entity change tracking
     }
 
     @Override
@@ -334,8 +342,6 @@ implements EntityFacet {
         getTransactionalProcessor()
         .runWithinCurrentTransactionElseCreateNew(()->pm.refresh(pojo))
         .ifFailureFail();
-
-        //TODO integrate with entity change tracking
     }
 
     @Override
@@ -359,12 +365,6 @@ implements EntityFacet {
         return jdoFacetContext.isMethodProvidedByEnhancement(method);
     }
 
-    // -- INTERACTION TRACKER LAZY LOOKUP
-
-    // memoizes the lookup, just an optimization
-//    private final _Lazy<InteractionLayerTracker> isisInteractionTrackerLazy = _Lazy.threadSafe(
-//            ()->getServiceRegistry().lookupServiceElseFail(InteractionLayerTracker.class));
-
     // -- DEPENDENCIES
 
     private PersistenceManager getPersistenceManager() {
@@ -381,25 +381,18 @@ implements EntityFacet {
         return txService;
     }
 
-//    private JdoPersistenceSession getJdoPersistenceSession() {
-//        return isisInteractionTrackerLazy.get().currentInteractionSession()
-//                .map(interactionSession->interactionSession.getAttribute(JdoPersistenceSession.class))
-//                .orElseThrow(()->_Exceptions.illegalState("no JdoPersistenceSession on current thread"));
-//    }
-
     // -- HELPER
 
     private Can<ManagedObject> fetchWithinTransaction(final Supplier<List<?>> fetcher) {
-
         return getTransactionalProcessor().callWithinCurrentTransactionElseCreateNew(
                 ()->_NullSafe.stream(fetcher.get())
-                    .map(fetchedObject->adopt(objectLifecyclePublisher, fetchedObject))
+                    .map(fetchedObject->adapt(objectLifecyclePublisher, fetchedObject))
                     .collect(Can.toCan()))
                 .ifFailureFail()
                 .getValue().orElseThrow();
     }
 
-    private ManagedObject adopt(
+    private ManagedObject adapt(
             final ObjectLifecyclePublisher objectLifecyclePublisher,
             final Object fetchedObject) {
         // handles lifecycle callbacks and injects services
@@ -409,7 +402,6 @@ implements EntityFacet {
         if(fetchedObject instanceof Persistable) {
             // an entity
             val entity = objectManager.adapt(fetchedObject);
-
             objectLifecyclePublisher.onPostLoad(entity);
             return entity;
         } else {
diff --git a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/_Util.java b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/_Util.java
index 189ea980e1..0a97b69afa 100644
--- a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/_Util.java
+++ b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/_Util.java
@@ -34,17 +34,18 @@ import javax.enterprise.inject.spi.InjectionTarget;
 import javax.inject.Provider;
 
 import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal._Constants;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 
 import lombok.SneakyThrows;
 
 final class _Util {
 
-    static <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual) {
+    static <T> CreationalContext<T> createCreationalContext(final Contextual<T> contextual) {
         return new CreationalContext<T>() {
 
             @Override
-            public void push(T incompleteInstance) {
+            public void push(final T incompleteInstance) {
                 // silently ignore
             }
 
@@ -56,7 +57,7 @@ final class _Util {
         };
     }
 
-    static <T> AnnotatedType<T> createAnnotatedType(Class<T> type) {
+    static <T> AnnotatedType<T> createAnnotatedType(final Class<T> type) {
 
         return new AnnotatedType<T>() {
 
@@ -78,7 +79,7 @@ final class _Util {
             }
 
             @Override
-            public <X extends Annotation> X getAnnotation(Class<X> annotationType) {
+            public <X extends Annotation> X getAnnotation(final Class<X> annotationType) {
                 _Exceptions.throwNotImplemented();
                 return null;
             }
@@ -90,7 +91,7 @@ final class _Util {
             }
 
             @Override
-            public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+            public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
                 _Exceptions.throwNotImplemented();
                 return false;
             }
@@ -122,17 +123,17 @@ final class _Util {
         return new InjectionTarget<T>() {
 
             @Override @SneakyThrows
-            public T produce(CreationalContext<T> ctx) {
-                return type.getJavaClass().newInstance();
+            public T produce(final CreationalContext<T> ctx) {
+                return type.getJavaClass().getConstructor(_Constants.emptyClasses).newInstance();
             }
 
             @Override
-            public void inject(T instance, CreationalContext<T> ctx) {
+            public void inject(final T instance, final CreationalContext<T> ctx) {
                 serviceInjectorProvider.get().injectServicesInto(instance);
             }
 
             @Override
-            public void dispose(T instance) {
+            public void dispose(final T instance) {
                 // silently ignore
             }
 
@@ -143,12 +144,12 @@ final class _Util {
             }
 
             @Override
-            public void postConstruct(T instance) {
+            public void postConstruct(final T instance) {
                 // silently ignore
             }
 
             @Override
-            public void preDestroy(T instance) {
+            public void preDestroy(final T instance) {
                 // silently ignore
             }
         };
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/HomePageReprRenderer.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/HomePageReprRenderer.java
index f30b81beb5..9f632da409 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/HomePageReprRenderer.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/HomePageReprRenderer.java
@@ -43,7 +43,7 @@ import lombok.val;
 public class HomePageReprRenderer
 extends ReprRendererAbstract<Void> {
 
-    // injection points not directly managed by Spring, instead resolved via constructor
+    // self managed injections via constructor
     @Inject BrandingUiService brandingUiService;
 
     HomePageReprRenderer(