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

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


The following commit(s) were added to refs/heads/main by this push:
     new 9961569386d CAUSEWAY-3883: WrapperInvocation to capture invocation 
specifics
9961569386d is described below

commit 9961569386d5a6de9e8d0ca7cfe0ef6862953c2a
Author: Andi Huber <[email protected]>
AuthorDate: Wed Jun 18 13:59:34 2025 +0200

    CAUSEWAY-3883: WrapperInvocation to capture invocation specifics
---
 .../applib/services/wrapper/WrappingObject.java    |   9 +-
 .../runtime/wrap/WrapperInvocationHandler.java     |  57 +++---
 .../wrapper/WrapperFactoryDefault.java             |  15 +-
 .../handlers/DomainObjectInvocationHandler.java    | 196 ++++++++++-----------
 .../wrapper/handlers/PluralInvocationHandler.java  |  18 +-
 .../wrapper/handlers/ProxyGenerator.java           |  31 +++-
 .../wrapper/WrapperFactoryDefaultTest.java         |   2 +-
 .../ProxyCreatorTestUsingCodegenPlugin.java        |  25 ++-
 8 files changed, 174 insertions(+), 179 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrappingObject.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrappingObject.java
index 1374dd7f01d..f5a1827c0f4 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrappingObject.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrappingObject.java
@@ -49,9 +49,12 @@ public interface WrappingObject {
     final static List<AdditionalField> ADDITIONAL_FIELDS = List.of(
             new AdditionalField(ORIGIN_FIELD_NAME, 
WrappingObject.Origin.class, Modifier.PROTECTED));
     
-    record Origin(Object pojo) {
-        public static Origin empty() {
-            return new Origin(null);
+    record Origin(Object pojo, SyncControl syncControl, boolean isFallback) {
+        public static Origin fallback(Object target) {
+            return new Origin(target, SyncControl.control().withNoExecute(), 
true);
+        }
+        public Origin(Object pojo, SyncControl syncControl) {
+            this(pojo, syncControl, false);
         }
     }
 
diff --git 
a/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
 
b/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
index 618d102cf94..92f9f6cf450 100644
--- 
a/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
+++ 
b/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
@@ -20,38 +20,35 @@
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.util.Objects;
 
 import org.jspecify.annotations.NonNull;
 
-import org.apache.causeway.applib.services.wrapper.WrapperFactory;
 import org.apache.causeway.applib.services.wrapper.WrappingObject;
-import org.apache.causeway.applib.services.wrapper.control.SyncControl;
-import org.apache.causeway.applib.services.wrapper.events.InteractionEvent;
+import org.apache.causeway.applib.services.wrapper.control.ExecutionMode;
 import org.apache.causeway.commons.internal._Constants;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
 
 public interface WrapperInvocationHandler extends InvocationHandler {
     
-    Context context();
+    ClassMetaData classMetaData();
     
-    default Method equalsMethod() { return context().equalsMethod(); }
-    default Method hashCodeMethod() { return context().hashCodeMethod(); }
-    default Method toStringMethod() { return context().toStringMethod(); }
+    Object invoke(WrapperInvocation wrapperInvocation) throws Throwable;
     
-    public record Context(
+    @Override
+    default Object invoke(Object target, Method method, Object[] args) throws 
Throwable {
+        return invoke(WrapperInvocation.of(target, method, args));
+    }
+    
+    public record ClassMetaData(
             /** underlying class that is to be proxied */
             Class<?> pojoClass,
-            WrapperFactory wrapperFactory,
-            SyncControl syncControl,
 
             Method equalsMethod,
             Method hashCodeMethod,
             Method toStringMethod) {
         
-        public static Context of(
-                final @NonNull MetaModelContext mmc,
-                final @NonNull Object pojo,
-                final SyncControl syncControl) {
+        public static ClassMetaData of(
+                final @NonNull Object pojo) {
 
             var pojoClass = pojo.getClass();
             try {
@@ -60,8 +57,7 @@ public static Context of(
                 var toStringMethod = pojoClass.getMethod("toString", 
_Constants.emptyClasses);
                 
                 return new WrapperInvocationHandler
-                        .Context(pojoClass, mmc.getWrapperFactory(), 
-                                syncControl, equalsMethod, hashCodeMethod, 
toStringMethod);
+                        .ClassMetaData(pojoClass, equalsMethod, 
hashCodeMethod, toStringMethod);
                 
             } catch (final NoSuchMethodException e) {
                 // ///CLOVER:OFF
@@ -70,21 +66,34 @@ public static Context of(
             }
         }
         
-        public WrappingObject.Origin origin(WrappingObject wrappingObject) {
-            return WrappingObject.getOrigin(wrappingObject);
-        }
-        
         public boolean isObjectMethod(final Method method) {
             return toStringMethod().equals(method) 
                     || hashCodeMethod().equals(method) 
                     || equalsMethod().equals(method);
         }
         
-        public InteractionEvent notifyListeners(final InteractionEvent 
interactionEvent) {
-            wrapperFactory().notifyListeners(interactionEvent);
-            return interactionEvent;
+    }
+    
+    public record WrapperInvocation(
+        WrappingObject.Origin origin,
+        Method method,
+        Object[] args) {
+
+        static WrapperInvocation of(Object target, Method method, Object[] 
args) {
+            Objects.requireNonNull(target);
+            var origin = target instanceof WrappingObject wrappingObject 
+                    ? WrappingObject.getOrigin(wrappingObject)
+                    : WrappingObject.Origin.fallback(target);
+            return new WrapperInvocation(origin, method, args);
         }
         
+        public boolean shouldEnforceRules() {
+            return 
!origin().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION);
+        }
+
+        public boolean shouldExecute() {
+            return 
!origin().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION);
+        }
     }
     
 }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
index 4bfb8cf8212..1d591931312 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
@@ -102,7 +102,6 @@
 import org.apache.causeway.core.runtimeservices.session.InteractionIdGenerator;
 import 
org.apache.causeway.core.runtimeservices.wrapper.dispatchers.InteractionEventDispatcher;
 import 
org.apache.causeway.core.runtimeservices.wrapper.dispatchers.InteractionEventDispatcherTypeSafe;
-import 
org.apache.causeway.core.runtimeservices.wrapper.handlers.DomainObjectInvocationHandler;
 import 
org.apache.causeway.core.runtimeservices.wrapper.handlers.ProxyGenerator;
 import org.apache.causeway.schema.cmd.v2.CommandDto;
 
@@ -301,12 +300,7 @@ public <T,R> T asyncWrap(
             }
 
             if (asyncControl.isCheckRules()) {
-                var doih = new DomainObjectInvocationHandler<>(
-                        domainObject,
-                        null, // mixeeAdapter ignored
-                        targetAdapter,
-                        control().withNoExecute(),
-                        null);
+                var doih = proxyGenerator.handlerForRegular(domainObject, 
targetAdapter);
                 doih.invoke(domainObject, method, args);
             }
 
@@ -347,12 +341,7 @@ public <T, R> T asyncWrapMixin(
             }
 
             if (asyncControl.isCheckRules()) {
-                var doih = new DomainObjectInvocationHandler<>(
-                        mixin,
-                        mixeeAdapter,
-                        mixinAdapter,
-                        control().withNoExecute(),
-                        null);
+                var doih = proxyGenerator.handlerForMixin(mixin, mixeeAdapter, 
mixinAdapter);
                 doih.invoke(mixin, method, args);
             }
 
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
index f02655ffd91..89d576f1185 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -21,7 +21,6 @@
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
@@ -31,8 +30,6 @@
 import org.apache.causeway.applib.services.wrapper.HiddenException;
 import org.apache.causeway.applib.services.wrapper.InvalidException;
 import org.apache.causeway.applib.services.wrapper.WrappingObject;
-import org.apache.causeway.applib.services.wrapper.control.ExecutionMode;
-import org.apache.causeway.applib.services.wrapper.control.SyncControl;
 import 
org.apache.causeway.applib.services.wrapper.events.CollectionAccessEvent;
 import org.apache.causeway.applib.services.wrapper.events.InteractionEvent;
 import org.apache.causeway.applib.services.wrapper.events.PropertyAccessEvent;
@@ -78,11 +75,11 @@
  * @param <T> type of delegate
  */
 @Log4j2
-public final class DomainObjectInvocationHandler<T>
+final class DomainObjectInvocationHandler<T>
 implements WrapperInvocationHandler {
 
     @Getter(onMethod_ = {@Override}) @Accessors(fluent=true) 
-    private final WrapperInvocationHandler.Context context;
+    private final WrapperInvocationHandler.ClassMetaData classMetaData;
     
     private final ProxyGenerator proxyGenerator;
     private final MetaModelContext mmc;
@@ -114,19 +111,15 @@ public DomainObjectInvocationHandler(
             final T domainObject,
             final ManagedObject mixeeAdapter, // ignored if not handling a 
mixin
             final ManagedObject targetAdapter,
-            final SyncControl syncControl,
             final ProxyGenerator proxyGenerator) {
         
         this.mmc = targetAdapter.objSpec().getMetaModelContext();
-        this.context = WrapperInvocationHandler.Context.of(
-                mmc,
-                domainObject,
-                syncControl);
+        this.classMetaData = 
WrapperInvocationHandler.ClassMetaData.of(domainObject);
         this.proxyGenerator = proxyGenerator;
 
         var _titleMethod = (Method)null;
         try {
-            _titleMethod = context().pojoClass().getMethod("title", 
_Constants.emptyClasses);
+            _titleMethod = classMetaData().pojoClass().getMethod("title", 
_Constants.emptyClasses);
         } catch (final NoSuchMethodException e) {
             // ignore
         }
@@ -146,52 +139,46 @@ public DomainObjectInvocationHandler(
         this.mixeeAdapter = mixeeAdapter;
     }
 
-    /**
-     * @param target - either the pojo directly or the proxy instance that is 
the target of invocation
-     * @param method - the method invoked on the proxy
-     * @param args - the args to the method invoked on the proxy
-     * @throws Throwable
-     */
     @Override
-    public Object invoke(final Object target, final Method method, final 
Object[] args) throws Throwable {
-
-        Objects.requireNonNull(target);
-        final var delegate = target instanceof WrappingObject wrappingObject 
-                ? context().origin(wrappingObject).pojo()
-                : target; // fallback to argument directly passed in
+    public Object invoke(WrapperInvocation wrapperInvocation) throws Throwable 
{
+    
+        final Object target = wrapperInvocation.origin().pojo();
+        final Method method = wrapperInvocation.method();
+        final Object[] args = wrapperInvocation.args();
+        var syncControl = wrapperInvocation.origin().syncControl();        
         
-        if (context().isObjectMethod(method)
+        if (classMetaData().isObjectMethod(method)
                 || isEnhancedEntityMethod(method)) {
-            return method.invoke(delegate, args);
+            return method.invoke(target, args);
         }
 
-        final ManagedObject targetAdapter = 
mmc.getObjectManager().adapt(delegate);
+        final ManagedObject targetAdapter = 
mmc.getObjectManager().adapt(target);
 
         if(!targetAdapter.specialization().isMixin()) {
             MmAssertionUtils.assertIsBookmarkSupported(targetAdapter);
         }
 
         if (method.equals(titleMethod)) {
-            return handleTitleMethod(targetAdapter);
+            return handleTitleMethod(wrapperInvocation, targetAdapter);
         }
 
         final ObjectSpecification targetSpec = targetAdapter.objSpec();
         var resolvedMethod = _GenericResolver.resolveMethod(method, 
targetSpec.getCorrespondingClass())
                 .orElseThrow();
 
-        if(target instanceof WrappingObject) {
+        if(!wrapperInvocation.origin().isFallback()) {
         
             if (method.equals(__causeway_originMethod)) {
-                return delegate;
+                return wrapperInvocation.origin();
             }
             
             // save method, through the proxy
             if (method.equals(__causeway_saveMethod)) {
-                return handleSaveMethod(targetAdapter, targetSpec);
+                return handleSaveMethod(wrapperInvocation, targetAdapter, 
targetSpec);
             }
     
             if (method.equals(__causeway_executionModes)) {
-                return context().syncControl().getExecutionModes();
+                return syncControl.getExecutionModes();
             }
         }
 
@@ -204,7 +191,7 @@ public Object invoke(final Object target, final Method 
method, final Object[] ar
         }
 
         if (intent == Intent.DEFAULTS || intent == 
Intent.CHOICES_OR_AUTOCOMPLETE) {
-            return method.invoke(delegate, args);
+            return method.invoke(target, args);
         }
 
         if (objectMember.isOneToOneAssociation()) {
@@ -216,11 +203,11 @@ public Object invoke(final Object target, final Method 
method, final Object[] ar
             final OneToOneAssociation otoa = (OneToOneAssociation) 
objectMember;
 
             if (intent == Intent.ACCESSOR) {
-                return handleGetterMethodOnProperty(targetAdapter, args, otoa);
+                return handleGetterMethodOnProperty(wrapperInvocation, 
targetAdapter, args, otoa);
             }
 
             if (intent == Intent.MODIFY_PROPERTY || intent == 
Intent.INITIALIZATION) {
-                return handleSetterMethodOnProperty(targetAdapter, args, otoa);
+                return handleSetterMethodOnProperty(wrapperInvocation, 
targetAdapter, args, otoa);
             }
         }
         if (objectMember.isOneToManyAssociation()) {
@@ -231,7 +218,7 @@ public Object invoke(final Object target, final Method 
method, final Object[] ar
 
             final OneToManyAssociation otma = (OneToManyAssociation) 
objectMember;
             if (intent == Intent.ACCESSOR) {
-                return handleGetterMethodOnCollection(targetAdapter, args, 
otma, memberId);
+                return handleGetterMethodOnCollection(wrapperInvocation, 
targetAdapter, args, otma, memberId);
             }
         }
 
@@ -255,13 +242,13 @@ public Object invoke(final Object target, final Method 
method, final Object[] ar
 
                 if (mixinMember != null) {
                     if(mixinMember instanceof ObjectAction) {
-                        return handleActionMethod(mixeeAdapter, args, 
(ObjectAction)mixinMember);
+                        return handleActionMethod(wrapperInvocation, 
mixeeAdapter, args, (ObjectAction)mixinMember);
                     }
                     if(mixinMember instanceof OneToOneAssociation) {
-                        return handleGetterMethodOnProperty(mixeeAdapter, new 
Object[0], (OneToOneAssociation)mixinMember);
+                        return handleGetterMethodOnProperty(wrapperInvocation, 
mixeeAdapter, new Object[0], (OneToOneAssociation)mixinMember);
                     }
                     if(mixinMember instanceof OneToManyAssociation) {
-                        return handleGetterMethodOnCollection(mixeeAdapter, 
new Object[0], (OneToManyAssociation)mixinMember, memberId);
+                        return 
handleGetterMethodOnCollection(wrapperInvocation, mixeeAdapter, new Object[0], 
(OneToManyAssociation)mixinMember, memberId);
                     }
                 } else {
                     throw _Exceptions.illegalState(String.format(
@@ -270,7 +257,7 @@ public Object invoke(final Object target, final Method 
method, final Object[] ar
             }
 
             // this is just a regular non-mixin action.
-            return handleActionMethod(targetAdapter, args, objectAction);
+            return handleActionMethod(wrapperInvocation, targetAdapter, args, 
objectAction);
         }
 
         throw new UnsupportedOperationException(String.format("Unknown member 
type '%s'", objectMember));
@@ -298,8 +285,8 @@ private static ObjectMember determineMixinMember(
         // throw new RuntimeException("Unable to find the mixed-in action 
corresponding to " + objectAction.getIdentifier().toFullIdentityString());
     }
 
-    public InteractionInitiatedBy getInteractionInitiatedBy() {
-        return shouldEnforceRules()
+    public InteractionInitiatedBy getInteractionInitiatedBy(final 
WrapperInvocation wrapperInvocation) {
+        return wrapperInvocation.shouldEnforceRules()
                 ? InteractionInitiatedBy.USER
                 : InteractionInitiatedBy.FRAMEWORK;
     }
@@ -310,28 +297,32 @@ private boolean isEnhancedEntityMethod(final Method 
method) {
                 : false;
     }
 
-    private Object handleTitleMethod(final ManagedObject targetAdapter) {
+    private Object handleTitleMethod(
+            final WrapperInvocation wrapperInvocation, 
+            final ManagedObject targetAdapter) {
 
         var targetNoSpec = targetAdapter.objSpec();
         var titleContext = targetNoSpec
                 .createTitleInteractionContext(targetAdapter, 
InteractionInitiatedBy.FRAMEWORK);
         var titleEvent = titleContext.createInteractionEvent();
-        context().notifyListeners(titleEvent);
+        mmc.getWrapperFactory().notifyListeners(titleEvent);
         return titleEvent.getTitle();
     }
 
     private Object handleSaveMethod(
-            final ManagedObject targetAdapter, final ObjectSpecification 
targetNoSpec) {
+            final WrapperInvocation wrapperInvocation, 
+            final ManagedObject targetAdapter, 
+            final ObjectSpecification targetNoSpec) {
 
-        runValidationTask(()->{
+        runValidationTask(wrapperInvocation, ()->{
             var interactionResult =
-                    targetNoSpec.isValidResult(targetAdapter, 
getInteractionInitiatedBy());
+                    targetNoSpec.isValidResult(targetAdapter, 
getInteractionInitiatedBy(wrapperInvocation));
             notifyListenersAndVetoIfRequired(interactionResult);
         });
 
         var spec = targetAdapter.objSpec();
         if(spec.isEntity()) {
-            return runExecutionTask(()->{
+            return runExecutionTask(wrapperInvocation, ()->{
                 MmEntityUtils.persistInCurrentTransaction(targetAdapter);
                 return null;
             });
@@ -341,24 +332,25 @@ private Object handleSaveMethod(
     }
 
     private Object handleGetterMethodOnProperty(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetAdapter,
             final Object[] args,
             final OneToOneAssociation property) {
 
         zeroArgsElseThrow(args, "get");
 
-        runValidationTask(()->{
-            checkVisibility(targetAdapter, property);
+        runValidationTask(wrapperInvocation, ()->{
+            checkVisibility(wrapperInvocation, targetAdapter, property);
         });
 
-        return runExecutionTask(()->{
+        return runExecutionTask(wrapperInvocation, ()->{
 
-            var interactionInitiatedBy = getInteractionInitiatedBy();
+            var interactionInitiatedBy = 
getInteractionInitiatedBy(wrapperInvocation);
             var currentReferencedAdapter = property.get(targetAdapter, 
interactionInitiatedBy);
 
             var currentReferencedObj = 
MmUnwrapUtils.single(currentReferencedAdapter);
 
-            context().notifyListeners(new PropertyAccessEvent(
+            mmc.getWrapperFactory().notifyListeners(new PropertyAccessEvent(
                     targetAdapter.getPojo(), 
                     property.getFeatureIdentifier(), 
                     currentReferencedObj));
@@ -369,34 +361,36 @@ private Object handleGetterMethodOnProperty(
     }
 
     private Object handleSetterMethodOnProperty(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetAdapter,
             final Object[] args,
             final OneToOneAssociation property) {
 
         var singleArg = singleArgUnderlyingElseNull(args, "setter");
 
-        runValidationTask(()->{
-            checkVisibility(targetAdapter, property);
-            checkUsability(targetAdapter, property);
+        runValidationTask(wrapperInvocation, ()->{
+            checkVisibility(wrapperInvocation, targetAdapter, property);
+            checkUsability(wrapperInvocation, targetAdapter, property);
         });
 
         var argumentAdapter = property.getObjectManager().adapt(singleArg);
 
-        runValidationTask(()->{
+        runValidationTask(wrapperInvocation, ()->{
             var interactionResult = property.isAssociationValid(
-                    targetAdapter, argumentAdapter, 
getInteractionInitiatedBy())
+                    targetAdapter, argumentAdapter, 
getInteractionInitiatedBy(wrapperInvocation))
                     .getInteractionResult();
             notifyListenersAndVetoIfRequired(interactionResult);
         });
 
-        return runExecutionTask(()->{
-            property.set(targetAdapter, argumentAdapter, 
getInteractionInitiatedBy());
+        return runExecutionTask(wrapperInvocation, ()->{
+            property.set(targetAdapter, argumentAdapter, 
getInteractionInitiatedBy(wrapperInvocation));
             return null;
         });
 
     }
 
     private Object handleGetterMethodOnCollection(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetAdapter,
             final Object[] args,
             final OneToManyAssociation collection,
@@ -404,13 +398,13 @@ private Object handleGetterMethodOnCollection(
 
         zeroArgsElseThrow(args, "get");
 
-        runValidationTask(()->{
-            checkVisibility(targetAdapter, collection);
+        runValidationTask(wrapperInvocation, ()->{
+            checkVisibility(wrapperInvocation, targetAdapter, collection);
         });
 
-        return runExecutionTask(()->{
+        return runExecutionTask(wrapperInvocation, ()->{
 
-            var interactionInitiatedBy = getInteractionInitiatedBy();
+            var interactionInitiatedBy = 
getInteractionInitiatedBy(wrapperInvocation);
             var currentReferencedAdapter = collection.get(targetAdapter, 
interactionInitiatedBy);
 
             var currentReferencedObj = 
MmUnwrapUtils.single(currentReferencedAdapter);
@@ -418,51 +412,47 @@ private Object handleGetterMethodOnCollection(
             var collectionAccessEvent = new 
CollectionAccessEvent(currentReferencedObj, collection.getFeatureIdentifier());
 
             if (currentReferencedObj instanceof Collection) {
-                var collectionViewObject = lookupWrappingObject(
-                        (Collection<?>) currentReferencedObj, collection);
-                context().notifyListeners(collectionAccessEvent);
+                var collectionViewObject = wrapCollection(
+                        (Collection<?>) currentReferencedObj, 
+                        collection);
+                mmc.getWrapperFactory().notifyListeners(collectionAccessEvent);
                 return collectionViewObject;
             } else if (currentReferencedObj instanceof Map) {
-                var mapViewObject = lookupWrappingObject((Map<?, ?>) 
currentReferencedObj,
+                var mapViewObject = wrapMap( 
+                        (Map<?, ?>) currentReferencedObj,
                         collection);
-                context().notifyListeners(collectionAccessEvent);
+                mmc.getWrapperFactory().notifyListeners(collectionAccessEvent);
                 return mapViewObject;
             }
 
             var msg = String.format("Collection type '%s' not supported by 
framework", currentReferencedObj.getClass().getName());
             throw new IllegalArgumentException(msg);
-
         });
 
     }
 
-    private Collection<?> lookupWrappingObject(
+    private Collection<?> wrapCollection(
             final Collection<?> collectionToLookup,
             final OneToManyAssociation otma) {
-        if (collectionToLookup instanceof WrappingObject) {
-            return collectionToLookup;
-        }
         if(proxyGenerator == null) {
             throw new IllegalStateException("Unable to create proxy for 
collection; "
                     + "proxyContextHandler not provided");
         }
-        return proxyGenerator.collectionProxy(collectionToLookup, 
context().syncControl(), otma);
+        return proxyGenerator.collectionProxy(collectionToLookup, otma);
     }
 
-    private Map<?, ?> lookupWrappingObject(
+    private Map<?, ?> wrapMap(
             final Map<?, ?> mapToLookup,
             final OneToManyAssociation otma) {
-        if (mapToLookup instanceof WrappingObject) {
-            return mapToLookup;
-        }
         if(proxyGenerator == null) {
             throw new IllegalStateException("Unable to create proxy for 
collection; "
                     + "proxyContextHandler not provided");
         }
-        return proxyGenerator.mapProxy(mapToLookup, context().syncControl(), 
otma);
+        return proxyGenerator.mapProxy(mapToLookup, otma);
     }
 
     private Object handleActionMethod(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetAdapter,
             final Object[] args,
             final ObjectAction objectAction) {
@@ -479,14 +469,14 @@ private Object handleActionMethod(
                     : ManagedObject.empty(paramSpec);
         }));
 
-        runValidationTask(()->{
-            checkVisibility(targetAdapter, objectAction);
-            checkUsability(targetAdapter, objectAction);
-            checkValidity(head, objectAction, argAdapters);
+        runValidationTask(wrapperInvocation, ()->{
+            checkVisibility(wrapperInvocation, targetAdapter, objectAction);
+            checkUsability(wrapperInvocation, targetAdapter, objectAction);
+            checkValidity(wrapperInvocation, head, objectAction, argAdapters);
         });
 
-        return runExecutionTask(()->{
-            var interactionInitiatedBy = getInteractionInitiatedBy();
+        return runExecutionTask(wrapperInvocation, ()->{
+            var interactionInitiatedBy = 
getInteractionInitiatedBy(wrapperInvocation);
 
             var returnedAdapter = objectAction.execute(
                     head, argAdapters,
@@ -498,12 +488,13 @@ private Object handleActionMethod(
     }
 
     private void checkValidity(
+            final WrapperInvocation wrapperInvocation,
             final ActionInteractionHead head,
             final ObjectAction objectAction,
             final Can<ManagedObject> argAdapters) {
 
         var interactionResult = objectAction
-                .isArgumentSetValid(head, argAdapters, 
getInteractionInitiatedBy())
+                .isArgumentSetValid(head, argAdapters, 
getInteractionInitiatedBy(wrapperInvocation))
                 .getInteractionResult();
         notifyListenersAndVetoIfRequired(interactionResult);
     }
@@ -523,21 +514,23 @@ private Object underlying(final Object arg) {
     private final Where where = Where.ANYWHERE;
 
     private void checkVisibility(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetObjectAdapter,
             final ObjectMember objectMember) {
 
-        var visibleConsent = objectMember.isVisible(targetObjectAdapter, 
getInteractionInitiatedBy(), where);
+        var visibleConsent = objectMember.isVisible(targetObjectAdapter, 
getInteractionInitiatedBy(wrapperInvocation), where);
         var interactionResult = visibleConsent.getInteractionResult();
         notifyListenersAndVetoIfRequired(interactionResult);
     }
 
     private void checkUsability(
+            final WrapperInvocation wrapperInvocation,
             final ManagedObject targetObjectAdapter,
             final ObjectMember objectMember) {
 
         var interactionResult = objectMember.isUsable(
                 targetObjectAdapter,
-                getInteractionInitiatedBy(),
+                getInteractionInitiatedBy(wrapperInvocation),
                 where)
                 .getInteractionResult();
         notifyListenersAndVetoIfRequired(interactionResult);
@@ -547,7 +540,8 @@ private void checkUsability(
 
     private void notifyListenersAndVetoIfRequired(final InteractionResult 
interactionResult) {
         var interactionEvent = interactionResult.getInteractionEvent();
-        context().notifyListeners(interactionEvent);
+        
+        mmc.getWrapperFactory().notifyListeners(interactionEvent);
         if (interactionEvent.isVeto()) {
             throw toException(interactionEvent);
         }
@@ -579,39 +573,31 @@ private InteractionException toException(final 
InteractionEvent interactionEvent
 
     // -- HELPER
 
-    private boolean shouldEnforceRules() {
-        return 
!context().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION);
-    }
-
-    private boolean shouldExecute() {
-        return 
!context().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION);
-    }
-
-    private void runValidationTask(final Runnable task) {
-        if(!shouldEnforceRules()) {
+    private void runValidationTask(final WrapperInvocation wrapperInvocation, 
final Runnable task) {
+        if(!wrapperInvocation.shouldEnforceRules()) {
             return;
         }
         try {
             task.run();
         } catch(Exception ex) {
-            handleException(ex);
+            handleException(wrapperInvocation, ex);
         }
     }
 
-    private <X> X runExecutionTask(final Supplier<X> task) {
-        if(!shouldExecute()) {
+    private <X> X runExecutionTask(final WrapperInvocation wrapperInvocation, 
final Supplier<X> task) {
+        if(!wrapperInvocation.shouldExecute()) {
             return null;
         }
         try {
             return task.get();
         } catch(Exception ex) {
-            return _Casts.uncheckedCast(handleException(ex));
+            return _Casts.uncheckedCast(handleException(wrapperInvocation, 
ex));
         }
     }
 
     @SneakyThrows
-    private Object handleException(final Exception ex) {
-        var exceptionHandler = context().syncControl().getExceptionHandler()
+    private Object handleException(WrapperInvocation wrapperInvocation, final 
Exception ex) {
+        var exceptionHandler = 
wrapperInvocation.origin().syncControl().getExceptionHandler()
                 .orElse(null);
 
         if(exceptionHandler==null) {
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/PluralInvocationHandler.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/PluralInvocationHandler.java
index d7fcbfdfcf7..40904c94911 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/PluralInvocationHandler.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/PluralInvocationHandler.java
@@ -18,11 +18,11 @@
  */
 package org.apache.causeway.core.runtimeservices.wrapper.handlers;
 
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.causeway.applib.services.wrapper.control.SyncControl;
 import 
org.apache.causeway.applib.services.wrapper.events.CollectionMethodEvent;
 import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.semantics.CollectionSemantics;
@@ -37,16 +37,15 @@
  */
 record PluralInvocationHandler<T, P>(
         P collectionOrMapToBeProxied,
-        WrapperInvocationHandler.Context context,
+        WrapperInvocationHandler.ClassMetaData classMetaData,
         OneToManyAssociation oneToManyAssociation,
         CollectionSemantics collectionSemantics
-        ) implements WrapperInvocationHandler {
+        ) implements InvocationHandler {
 
     // -- FACTORIES
     
    static <T, C extends Collection<?>> PluralInvocationHandler<T, C> 
forCollection(
            final C collectionToBeProxied,
-           final SyncControl syncControl,
            final OneToManyAssociation otma) {
        
        
_Assert.assertTrue(Collection.class.isAssignableFrom(collectionToBeProxied.getClass()),
@@ -54,14 +53,13 @@ static <T, C extends Collection<?>> 
PluralInvocationHandler<T, C> forCollection(
                        PluralInvocationHandler.class.getName() + 
".forCollection(..)",
                        collectionToBeProxied.getClass()));
        
-       return new PluralInvocationHandler<>(collectionToBeProxied, 
syncControl, otma,
+       return new PluralInvocationHandler<>(collectionToBeProxied, otma,
                 CollectionSemantics
                     .valueOfElseFail(collectionToBeProxied.getClass()));
     }
     
    static <T, M extends Map<?,?>> PluralInvocationHandler<T, M> forMap(
            final M mapToBeProxied,
-           final SyncControl syncControl,
            final OneToManyAssociation otma) {
 
        
_Assert.assertTrue(Map.class.isAssignableFrom(mapToBeProxied.getClass()),
@@ -69,7 +67,7 @@ static <T, M extends Map<?,?>> PluralInvocationHandler<T, M> 
forMap(
                        PluralInvocationHandler.class.getName() + ".forMap(..)",
                        mapToBeProxied.getClass()));
        
-       return new PluralInvocationHandler<>(mapToBeProxied, syncControl, otma,
+       return new PluralInvocationHandler<>(mapToBeProxied, otma,
                CollectionSemantics.MAP);
    }
    
@@ -77,13 +75,11 @@ static <T, M extends Map<?,?>> PluralInvocationHandler<T, 
M> forMap(
     
     private PluralInvocationHandler(
             final P collectionOrMapToBeProxied,
-            final SyncControl syncControl,
             final OneToManyAssociation otma,
             final CollectionSemantics collectionSemantics) {
         
         this(collectionOrMapToBeProxied, 
-                
WrapperInvocationHandler.Context.of(otma.getMetaModelContext(), 
-                        collectionOrMapToBeProxied, syncControl), 
+                
WrapperInvocationHandler.ClassMetaData.of(collectionOrMapToBeProxied), 
                 otma, collectionSemantics);
     }
 
@@ -103,7 +99,7 @@ public Object invoke(final Object collectionObject, final 
Method method, final O
                             method.getName(),
                             args,
                             returnValueObj);
-            context().notifyListeners(event);
+            oneToManyAssociation().getWrapperFactory().notifyListeners(event);
             return returnValueObj;
         }
 
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyGenerator.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyGenerator.java
index 245827857b1..259038c1d88 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyGenerator.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyGenerator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.core.runtimeservices.wrapper.handlers;
 
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Proxy;
 import java.util.Collection;
 import java.util.Map;
@@ -45,10 +46,9 @@ public <T> T objectProxy(
             domainObject,
             null, // mixeeAdapter ignored
             adapter,
-            syncControl,
             this);
 
-        return instantiateProxy(invocationHandler, new 
WrappingObject.Origin(domainObject));
+        return instantiateProxy(invocationHandler, new 
WrappingObject.Origin(domainObject, syncControl));
     }
 
     public <T> T mixinProxy(
@@ -61,10 +61,9 @@ public <T> T mixinProxy(
                 mixin,
                 mixeeAdapter,
                 mixinAdapter,
-                syncControl,
                 this);
     
-        return instantiateProxy(invocationHandler, new 
WrappingObject.Origin(mixin));
+        return instantiateProxy(invocationHandler, new 
WrappingObject.Origin(mixin, syncControl));
     }
     
     /**
@@ -73,11 +72,10 @@ public <T> T mixinProxy(
      */
     public <T, E> Collection<E> collectionProxy(
             final Collection<E> collectionToBeProxied,
-            final SyncControl syncControl,
             final OneToManyAssociation otma) {
     
         var collectionInvocationHandler = PluralInvocationHandler
-            .forCollection(collectionToBeProxied, syncControl, otma);
+            .forCollection(collectionToBeProxied, otma);
     
         var proxyBase = CollectionSemantics
             .valueOfElseFail(collectionToBeProxied.getClass())
@@ -93,19 +91,18 @@ public <T, E> Collection<E> collectionProxy(
      */
     public <T, P, Q> Map<P, Q> mapProxy(
             final Map<P, Q> mapToBeProxied,
-            final SyncControl syncControl,
             final OneToManyAssociation otma) {
     
         var proxyBase = Map.class;
     
         return instantiatePluralProxy(_Casts.uncheckedCast(proxyBase), 
-                PluralInvocationHandler.forMap(mapToBeProxied, syncControl, 
otma));
+                PluralInvocationHandler.forMap(mapToBeProxied, otma));
     }
     
     // -- HELPER
 
     <T> T instantiateProxy(final WrapperInvocationHandler handler, 
WrappingObject.Origin origin) {
-        return 
_Casts.uncheckedCast(instantiateProxy(handler.context().pojoClass(), handler, 
origin));
+        return 
_Casts.uncheckedCast(instantiateProxy(handler.classMetaData().pojoClass(), 
handler, origin));
     }
 
     /**
@@ -128,5 +125,21 @@ private <T, P> P instantiatePluralProxy(final Class<T> 
base, final PluralInvocat
                 pluralInvocationHandler); 
         return _Casts.uncheckedCast(proxyWithoutFields);
     }
+
+    public <T> InvocationHandler handlerForRegular(@NonNull T domainObject, 
ManagedObject targetAdapter) {
+        return new DomainObjectInvocationHandler<>(
+                domainObject,
+                null, // mixeeAdapter ignored
+                targetAdapter,
+                null);
+    }
+
+    public <T> InvocationHandler handlerForMixin(T mixin, ManagedObject 
mixeeAdapter, ManagedObject mixinAdapter) {
+        return new DomainObjectInvocationHandler<>(
+                mixin,
+                mixeeAdapter,
+                mixinAdapter,
+                null);
+    }
     
 }
diff --git 
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
 
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
index 57176acd31a..3534620326a 100644
--- 
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
+++ 
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
@@ -55,7 +55,7 @@ public void __causeway_save() {
 
         @Override
         public WrappingObject.Origin __causeway_origin() {
-            return new WrappingObject.Origin(wrappedObject);
+            return new WrappingObject.Origin(wrappedObject, 
SyncControl.control());
         }
 
         @Override
diff --git 
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyCreatorTestUsingCodegenPlugin.java
 
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyCreatorTestUsingCodegenPlugin.java
index 32732eb18a9..d562f6887ac 100644
--- 
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyCreatorTestUsingCodegenPlugin.java
+++ 
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/ProxyCreatorTestUsingCodegenPlugin.java
@@ -18,7 +18,6 @@
  */
 package org.apache.causeway.core.runtimeservices.wrapper.handlers;
 
-import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -32,6 +31,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.causeway.applib.services.wrapper.WrappingObject;
+import org.apache.causeway.applib.services.wrapper.control.SyncControl;
 import 
org.apache.causeway.core.codegen.bytebuddy.services.ProxyFactoryServiceByteBuddy;
 import org.apache.causeway.core.runtime.wrap.WrapperInvocationHandler;
 
@@ -60,16 +60,9 @@ public void setName(final String name) {
     private static class WrapperInvocationHandlerForTest implements 
WrapperInvocationHandler {
         private final Employee delegate = new Employee();
         private final Set<String> invoked = new HashSet<>();
-        private final WrapperInvocationHandler.Context context = new 
WrapperInvocationHandler.Context(
-                Employee.class, null, null, null, null, null);
+        private final WrapperInvocationHandler.ClassMetaData classMetaData = 
new WrapperInvocationHandler.ClassMetaData(
+                Employee.class, null, null, null);
                 
-
-        @Override
-        public Object invoke(final Object proxy, final Method method, final 
Object[] args) throws Throwable {
-            invoked.add(method.getName());
-            return "hi";
-        }
-
         @Getter @Setter 
         private boolean resolveObjectChangedEnabled = false;
 
@@ -78,8 +71,14 @@ public boolean wasInvoked(final String methodName) {
         }
 
         @Override
-        public WrapperInvocationHandler.Context context() {
-            return context;
+        public WrapperInvocationHandler.ClassMetaData classMetaData() {
+            return classMetaData;
+        }
+
+        @Override
+        public Object invoke(WrapperInvocation wrapperInvocation) throws 
Throwable {
+            invoked.add(wrapperInvocation.method().getName());
+            return "hi";
         }
         
     }
@@ -88,7 +87,7 @@ public WrapperInvocationHandler.Context context() {
     void proxyShouldDelegateCalls() {
 
         final WrapperInvocationHandlerForTest handler = new 
WrapperInvocationHandlerForTest();
-        final Employee proxyOfEmployee = 
proxyGenerator.instantiateProxy(handler, new 
WrappingObject.Origin(handler.delegate));
+        final Employee proxyOfEmployee = 
proxyGenerator.instantiateProxy(handler, new 
WrappingObject.Origin(handler.delegate, SyncControl.control()));
 
         assertNotNull(proxyOfEmployee);
 


Reply via email to