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 2021/01/12 14:35:33 UTC

[isis] branch master updated: ISIS-2033: adds support for injection point resolving on JPA entities

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 0c62ece  ISIS-2033: adds support for injection point resolving on JPA entities
0c62ece is described below

commit 0c62ecec3ac4306d4f7ab7bc5e881946fab91873
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jan 12 15:35:16 2021 +0100

    ISIS-2033: adds support for injection point resolving on JPA entities
---
 .../commons/internal/exceptions/_Exceptions.java   |   1 +
 .../JpaEntityInjectionPointResolver.java           |  73 +++++
 .../jpa/eclipselink/IsisModuleJpaEclipseLink.java  |   9 +-
 .../inject/BeanManagerForEntityListeners.java      | 327 +++++++++++++++++++++
 .../persistence/jpa/eclipselink/inject/_Util.java  | 157 ++++++++++
 .../testdomain/conf/Configuration_usingJpa.java    |   4 +
 .../isis/testdomain/jpa/entities/JpaBook.java      |  27 ++
 .../isis/testdomain/jpa/entities/JpaInventory.java |   1 -
 .../injecting/jpa/JpaEntityInjectingTest.java      | 148 ++++++++++
 .../wrapper/jdo/isis/JdoIsisWrapperSyncTest.java   |   4 +-
 10 files changed, 746 insertions(+), 5 deletions(-)

diff --git a/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java b/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
index 51c5d07..139a3ab 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
@@ -225,6 +225,7 @@ public final class _Exceptions {
      *
      */
     public static void throwNotImplemented() {
+        dumpStackTrace();
         throw notImplemented();
     }
 
diff --git a/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/JpaEntityInjectionPointResolver.java b/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/JpaEntityInjectionPointResolver.java
new file mode 100644
index 0000000..5bbc698
--- /dev/null
+++ b/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/JpaEntityInjectionPointResolver.java
@@ -0,0 +1,73 @@
+/*
+ *  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.persistence.jpa.applib.integration;
+
+import javax.inject.Inject;
+import javax.persistence.PostLoad;
+import javax.persistence.PostPersist;
+import javax.persistence.PostRemove;
+import javax.persistence.PostUpdate;
+import javax.persistence.PrePersist;
+import javax.persistence.PreRemove;
+import javax.persistence.PreUpdate;
+
+import org.apache.isis.applib.services.inject.ServiceInjector;
+
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * EntityListener class for listing with the {@link javax.persistence.EntityListeners} annotation, to
+ * support injection point resolving for entities.
+ * <p>
+ * Instances of this class are not managed by Spring, but by the persistence layer. 
+ * <p>
+ * The particular persistence layer implementation in use needs to be configured, 
+ * with a BeanManager, that is able to resolve injection points for this EntityListener.   
+ *  
+ * @since 2.0
+ */
+@Log4j2
+public class JpaEntityInjectionPointResolver {
+
+    @Inject // not managed by Spring (directly)
+    private ServiceInjector serviceInjector;
+    
+    @PrePersist
+    @PreUpdate
+    @PreRemove
+    private void beforeAnyUpdate(Object entityPojo) {
+        log.debug("beforeAnyUpdate: {}", entityPojo);
+        serviceInjector.injectServicesInto(entityPojo);
+    }
+    
+    @PostPersist
+    @PostUpdate
+    @PostRemove
+    private void afterAnyUpdate(Object entityPojo) {
+        log.debug("afterAnyUpdate: {}" + entityPojo);
+        //serviceInjector.injectServicesInto(entityPojo);
+    }
+    
+    @PostLoad
+    private void afterLoad(Object entityPojo) {
+        log.debug("afterLoad: {}" + entityPojo);
+        serviceInjector.injectServicesInto(entityPojo);
+    }
+    
+}
diff --git a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/IsisModuleJpaEclipseLink.java b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/IsisModuleJpaEclipseLink.java
index 144ad30..c8c3f0d 100644
--- a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/IsisModuleJpaEclipseLink.java
+++ b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/IsisModuleJpaEclipseLink.java
@@ -21,6 +21,8 @@ package org.apache.isis.persistence.jpa.eclipselink;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.inject.Inject;
+import javax.inject.Provider;
 import javax.sql.DataSource;
 
 import org.eclipse.persistence.config.PersistenceUnitProperties;
@@ -33,6 +35,8 @@ import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
 import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
 import org.springframework.transaction.jta.JtaTransactionManager;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.persistence.jpa.eclipselink.inject.BeanManagerForEntityListeners;
 import org.apache.isis.persistence.jpa.integration.IsisModuleJpaIntegration;
 
 /**
@@ -47,6 +51,8 @@ import org.apache.isis.persistence.jpa.integration.IsisModuleJpaIntegration;
 })
 public class IsisModuleJpaEclipseLink extends JpaBaseConfiguration { 
 
+    @Inject private Provider<ServiceInjector> serviceInjectorProvider;
+    
     protected IsisModuleJpaEclipseLink(
             DataSource dataSource, 
             JpaProperties properties,
@@ -59,12 +65,13 @@ public class IsisModuleJpaEclipseLink extends JpaBaseConfiguration {
         return new EclipseLinkJpaVendorAdapter(); 
     }
 
-    //TODO[2033] this is application specific configuration that belongs to application.yaml
+    //TODO[2033] partly application specific configuration that belongs to application.yaml
     @Override
     protected Map<String, Object> getVendorProperties() {
         HashMap<String, Object> map = new HashMap<>();
         map.put(PersistenceUnitProperties.WEAVING, "false");
         map.put(PersistenceUnitProperties.DDL_GENERATION, PersistenceUnitProperties.DROP_AND_CREATE);
+        map.put(PersistenceUnitProperties.CDI_BEANMANAGER, new BeanManagerForEntityListeners(serviceInjectorProvider));
         return map;
     }
 
diff --git a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/BeanManagerForEntityListeners.java b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/BeanManagerForEntityListeners.java
new file mode 100644
index 0000000..c131e1a
--- /dev/null
+++ b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/BeanManagerForEntityListeners.java
@@ -0,0 +1,327 @@
+/*
+ *  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.persistence.jpa.eclipselink.inject;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Event;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMember;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedParameter;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanAttributes;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Decorator;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.InjectionTargetFactory;
+import javax.enterprise.inject.spi.InterceptionFactory;
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.enterprise.inject.spi.ObserverMethod;
+import javax.enterprise.inject.spi.ProducerFactory;
+import javax.inject.Provider;
+
+import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Incomplete implementation of a {@link BeanManager}, solely for the purpose of enabling
+ * injection point resolving on javax.persistence.EntityListeners.
+ * <p>
+ * Classes listed with the {@link javax.persistence.EntityListeners} annotation are not managed
+ * by Spring, hence injection point resolving for these is not supported out of the box. However, 
+ * EclipseLink allows to configure a {@link BeanManager}, that is used for injection point 
+ * resolving. This implementation is limited to support only no-arg constructors.
+ *  
+ * @since 2.0 
+ */
+@RequiredArgsConstructor
+public class BeanManagerForEntityListeners implements BeanManager {
+
+    private final Provider<ServiceInjector> serviceInjectorProvider;
+    
+    @Override
+    public <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual) {
+        return _Util.createCreationalContext(contextual);
+    }
+    
+    @Override
+    public <T> AnnotatedType<T> createAnnotatedType(Class<T> type) {
+        return _Util.createAnnotatedType(type);
+    }
+    
+    @Override
+    public <T> InjectionTarget<T> createInjectionTarget(AnnotatedType<T> type) {
+        return _Util.createInjectionTarget(type, serviceInjectorProvider);
+    }
+    
+    // -- IGNORED
+    
+    @Override
+    public Object getReference(Bean<?> bean, Type beanType, CreationalContext<?> ctx) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Object getInjectableReference(InjectionPoint ij, CreationalContext<?> ctx) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Set<Bean<?>> getBeans(Type beanType, Annotation... qualifiers) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Set<Bean<?>> getBeans(String name) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Bean<?> getPassivationCapableBean(String id) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <X> Bean<? extends X> resolve(Set<Bean<? extends X>> beans) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public void validate(InjectionPoint injectionPoint) {
+        _Exceptions.throwNotImplemented();
+    }
+
+    @Override
+    public void fireEvent(Object event, Annotation... qualifiers) {
+        _Exceptions.throwNotImplemented();
+    }
+
+    @Override
+    public <T> Set<ObserverMethod<? super T>> resolveObserverMethods(T event, Annotation... qualifiers) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public List<Decorator<?>> resolveDecorators(Set<Type> types, Annotation... qualifiers) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public List<Interceptor<?>> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public boolean isScope(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean isNormalScope(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean isPassivatingScope(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean isQualifier(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean isInterceptorBinding(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean isStereotype(Class<? extends Annotation> annotationType) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public Set<Annotation> getInterceptorBindingDefinition(Class<? extends Annotation> bindingType) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Set<Annotation> getStereotypeDefinition(Class<? extends Annotation> stereotype) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public boolean areQualifiersEquivalent(Annotation qualifier1, Annotation qualifier2) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public boolean areInterceptorBindingsEquivalent(Annotation interceptorBinding1,
+            Annotation interceptorBinding2) {
+        _Exceptions.throwNotImplemented();
+        return false;
+    }
+
+    @Override
+    public int getQualifierHashCode(Annotation qualifier) {
+        _Exceptions.throwNotImplemented();
+        return 0;
+    }
+
+    @Override
+    public int getInterceptorBindingHashCode(Annotation interceptorBinding) {
+        _Exceptions.throwNotImplemented();
+        return 0;
+    }
+
+    @Override
+    public Context getContext(Class<? extends Annotation> scopeType) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public ELResolver getELResolver() {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public ExpressionFactory wrapExpressionFactory(ExpressionFactory expressionFactory) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T> InjectionTargetFactory<T> getInjectionTargetFactory(AnnotatedType<T> annotatedType) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <X> ProducerFactory<X> getProducerFactory(AnnotatedField<? super X> field, Bean<X> declaringBean) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <X> ProducerFactory<X> getProducerFactory(AnnotatedMethod<? super X> method, Bean<X> declaringBean) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T> BeanAttributes<T> createBeanAttributes(AnnotatedType<T> type) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public BeanAttributes<?> createBeanAttributes(AnnotatedMember<?> type) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T> Bean<T> createBean(BeanAttributes<T> attributes, Class<T> beanClass,
+            InjectionTargetFactory<T> injectionTargetFactory) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T, X> Bean<T> createBean(BeanAttributes<T> attributes, Class<X> beanClass,
+            ProducerFactory<X> producerFactory) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public InjectionPoint createInjectionPoint(AnnotatedField<?> field) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public InjectionPoint createInjectionPoint(AnnotatedParameter<?> parameter) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T extends Extension> T getExtension(Class<T> extensionClass) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public <T> InterceptionFactory<T> createInterceptionFactory(CreationalContext<T> ctx, Class<T> clazz) {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Event<Object> getEvent() {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    @Override
+    public Instance<Object> createInstance() {
+        _Exceptions.throwNotImplemented();
+        return null;
+    }
+
+    
+}
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
new file mode 100644
index 0000000..ce9fbdd
--- /dev/null
+++ b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/inject/_Util.java
@@ -0,0 +1,157 @@
+/*
+ *  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.persistence.jpa.eclipselink.inject;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.AnnotatedConstructor;
+import javax.enterprise.inject.spi.AnnotatedField;
+import javax.enterprise.inject.spi.AnnotatedMethod;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.inject.Provider;
+
+import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+
+import lombok.SneakyThrows;
+
+final class _Util {
+
+    static <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual) {
+        return new CreationalContext<T>() {
+        
+            @Override
+            public void push(T incompleteInstance) {
+                // silently ignore
+            }
+
+            @Override
+            public void release() {
+                // silently ignore
+            }
+            
+        };
+    }
+    
+    static <T> AnnotatedType<T> createAnnotatedType(Class<T> type) {
+        
+        return new AnnotatedType<T>() {
+
+            @Override
+            public Class<T> getJavaClass() {
+                return type;
+            }
+            
+            @Override
+            public Type getBaseType() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public Set<Type> getTypeClosure() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public <X extends Annotation> X getAnnotation(Class<X> annotationType) {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public Set<Annotation> getAnnotations() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+                _Exceptions.throwNotImplemented();
+                return false;
+            }
+
+            @Override
+            public Set<AnnotatedConstructor<T>> getConstructors() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public Set<AnnotatedMethod<? super T>> getMethods() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+
+            @Override
+            public Set<AnnotatedField<? super T>> getFields() {
+                _Exceptions.throwNotImplemented();
+                return null;
+            }
+        };
+    }
+    
+    static <T> InjectionTarget<T> createInjectionTarget(
+            final AnnotatedType<T> type,
+            final Provider<ServiceInjector> serviceInjectorProvider) {
+        
+        return new InjectionTarget<T>() {
+
+            @Override @SneakyThrows
+            public T produce(CreationalContext<T> ctx) {
+                return type.getJavaClass().newInstance();
+            }
+
+            @Override
+            public void inject(T instance, CreationalContext<T> ctx) {
+                serviceInjectorProvider.get().injectServicesInto(instance);
+            }
+            
+            @Override
+            public void dispose(T instance) {
+                // silently ignore
+            }
+
+            @Override
+            public Set<InjectionPoint> getInjectionPoints() {
+                // silently ignore
+                return Collections.emptySet();
+            }
+
+            @Override
+            public void postConstruct(T instance) {
+                // silently ignore
+            }
+
+            @Override
+            public void preDestroy(T instance) {
+                // silently ignore
+            }
+        };
+    }
+    
+}
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
index 27719e1..92b0d97f 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
@@ -31,6 +31,7 @@ import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
 import org.apache.isis.persistence.jpa.eclipselink.IsisModuleJpaEclipseLink;
 import org.apache.isis.security.bypass.IsisModuleSecurityBypass;
 import org.apache.isis.testdomain.jpa.JpaTestDomainModule;
+import org.apache.isis.testdomain.model.stereotypes.MyService;
 import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
 import org.apache.isis.testing.fixtures.applib.IsisModuleTestingFixturesApplib;
 
@@ -40,6 +41,9 @@ import org.apache.isis.testing.fixtures.applib.IsisModuleTestingFixturesApplib;
 
 //@Configuration
 @Import({
+    
+    MyService.class, // testing injection into entities
+    
     IsisModuleCoreRuntimeServices.class
     ,IsisModuleSecurityBypass.class
     ,IsisModuleJpaEclipseLink.class
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaBook.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaBook.java
index e1f7c75..f3db754 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaBook.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaBook.java
@@ -18,22 +18,31 @@
  */
 package org.apache.isis.testdomain.jpa.entities;
 
+import javax.inject.Inject;
 import javax.persistence.Column;
 import javax.persistence.DiscriminatorValue;
 import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.Transient;
 
 import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.Nature;
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.annotation.Publishing;
+import org.apache.isis.persistence.jpa.applib.integration.JpaEntityInjectionPointResolver;
+import org.apache.isis.testdomain.model.stereotypes.MyService;
+import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
 
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
 
 @Entity
+@EntityListeners(JpaEntityInjectionPointResolver.class)
 @DiscriminatorValue("Book")
 @DomainObject(
         objectType = "testdomain.jpa.Book",
@@ -41,8 +50,25 @@ import lombok.ToString;
         entityChangePublishing = Publishing.ENABLED)
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @ToString(callSuper = true)
+@Log4j2
 public class JpaBook extends JpaProduct {
 
+    @Inject @Transient private KVStoreForTesting kvStore;
+    
+    // -- ENTITY SERVICE INJECTION TEST
+    @Transient private MyService myService;
+    @Inject 
+    public void setMyService(MyService myService) {
+        val count = kvStore.incrementCounter(JpaBook.class, "injection-count");
+        log.debug("INJECTION " + count);
+        this.myService = myService;
+    }
+    public boolean hasInjectionPointsResolved() {
+        getAuthor(); // seems to have the required side-effect to actually trigger injection
+        return myService != null;
+    }
+    // --
+    
     @Override
     public String title() {
         return toString();
@@ -86,4 +112,5 @@ public class JpaBook extends JpaProduct {
         this.isbn = isbn;
         this.publisher = publisher;
     }
+
 }
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaInventory.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaInventory.java
index 72edde8..cc101b3 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaInventory.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/jpa/entities/JpaInventory.java
@@ -30,7 +30,6 @@ import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
 
 import org.apache.isis.applib.annotation.DomainObject;
-import org.apache.isis.applib.annotation.DomainObjectLayout;
 import org.apache.isis.applib.annotation.Nature;
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.annotation.Publishing;
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/injecting/jpa/JpaEntityInjectingTest.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/injecting/jpa/JpaEntityInjectingTest.java
new file mode 100644
index 0000000..b36d13a
--- /dev/null
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/injecting/jpa/JpaEntityInjectingTest.java
@@ -0,0 +1,148 @@
+/*
+ *  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.testdomain.injecting.jpa;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.isis.applib.services.repository.RepositoryService;
+import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.commons.internal.primitives._Longs.Bound;
+import org.apache.isis.commons.internal.primitives._Longs.Range;
+import org.apache.isis.core.config.presets.IsisPresets;
+import org.apache.isis.testdomain.conf.Configuration_usingJpa;
+import org.apache.isis.testdomain.jpa.JpaTestDomainPersona;
+import org.apache.isis.testdomain.jpa.entities.JpaBook;
+import org.apache.isis.testdomain.jpa.entities.JpaProduct;
+import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
+import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
+import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
+
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+@SpringBootTest(
+        classes = { 
+                Configuration_usingJpa.class
+        },
+        properties = {
+//                "logging.level.org.apache.isis.persistence.jdo.integration.changetracking.JdoLifecycleListener=DEBUG",
+//                "logging.level.org.apache.isis.testdomain.injecting.jdo.isis.JdoIsisEntityInjectingTest=DEBUG"
+        }
+)
+@TestPropertySource(IsisPresets.UseLog4j2Test)
+@Transactional 
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@Log4j2
+class JpaEntityInjectingTest extends IsisIntegrationTestAbstract {
+
+    @Inject private FixtureScripts fixtureScripts;
+    @Inject private RepositoryService repository;
+    @Inject private KVStoreForTesting kvStore;
+
+    @Test @Order(0) @Rollback(false)
+    void init() {
+        
+        // cleanup
+        fixtureScripts.runPersona(JpaTestDomainPersona.PurgeAll);
+        kvStore.clear(JpaBook.class);
+        
+        // given
+        fixtureScripts.runPersona(JpaTestDomainPersona.InventoryWith1Book);
+        assertInjectCountRange(1, 2);
+    }
+    
+
+    @Test @Order(1)
+    void sampleBook_shouldHave_injectionPointsResolved() {
+        log.debug("TEST 1 ENTERING");
+        
+        //assertInjectCountRange(1, 2);
+        
+        val book = getSampleBook();        
+        assertTrue(book.hasInjectionPointsResolved());
+        
+        //assertInjectCountRange(1, 3);
+        
+        log.debug("TEST 1 EXITING");
+    }
+    
+    @Test @Order(2)
+    void sampleBook_shouldHave_injectionPointsResolved_whenFetchedAgain() {
+        
+        log.debug("TEST 2 ENTERING");
+        
+        //assertInjectCountRange(1, 2);
+        
+        val book = getSampleBook();
+        assertTrue(book.hasInjectionPointsResolved());
+        
+        //assertInjectCountRange(1, 3);
+        
+        log.debug("TEST 2 EXITING");
+        
+    }
+    
+    @Test @Order(3)
+    void sampleBook_shouldHave_injectionPointsResolved_whenFetchedAgain2() {
+        
+        log.debug("TEST 3 ENTERING");
+        
+        //assertInjectCountRange(1, 3);
+        
+        val book = getSampleBook();
+        assertTrue(book.hasInjectionPointsResolved());
+        
+        //assertInjectCountRange(1, 4);
+        
+        log.debug("TEST 3 EXITING");
+    }
+    
+    // -- HELPER
+    
+    private long getInjectCount() {
+        return kvStore.getCounter(JpaBook.class, "injection-count");
+    }
+    
+    private JpaBook getSampleBook() {
+        val books = repository.allInstances(JpaProduct.class);
+        assertEquals(1, books.size(), "book count");
+        val book = books.get(0);
+        assertEquals("Sample Book", book.getName(), "book name");
+        return (JpaBook)book;
+    }
+    
+    private void assertInjectCountRange(long lower, long upper) {
+        _Assert.assertRangeContains(
+                Range.of(Bound.inclusive(lower), Bound.inclusive(upper)), 
+                getInjectCount(), "injection count");
+    }
+    
+}
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/wrapper/jdo/isis/JdoIsisWrapperSyncTest.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/wrapper/jdo/isis/JdoIsisWrapperSyncTest.java
index 16b74ac..9d4e1e4 100644
--- a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/wrapper/jdo/isis/JdoIsisWrapperSyncTest.java
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/wrapper/jdo/isis/JdoIsisWrapperSyncTest.java
@@ -23,7 +23,6 @@ import java.util.concurrent.ExecutionException;
 import javax.inject.Inject;
 
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.TestPropertySource;
@@ -50,7 +49,6 @@ import lombok.val;
         }
 )
 @TestPropertySource(IsisPresets.UseLog4j2Test)
-//@Incubating("wrapper.wrap(inventoryManager) throws NPE")
 class JdoIsisWrapperSyncTest extends IsisIntegrationTestAbstract {
 
     @Inject private FixtureScripts fixtureScripts;
@@ -67,7 +65,7 @@ class JdoIsisWrapperSyncTest extends IsisIntegrationTestAbstract {
         fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
     }
 
-    @Test @Tag("Incubating")
+    @Test
     void testWrapper_waitingOnDomainEvent() throws InterruptedException, ExecutionException {
 
         val inventoryManager = facoryService.viewModel(JdoInventoryManager.class);