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 cf05433a7e5 CAUSEWAY-3859: regression fix for composite value support
cf05433a7e5 is described below

commit cf05433a7e546ccb343af0a53fd980e2b9375208
Author: Andi Huber <[email protected]>
AuthorDate: Fri Feb 28 10:02:18 2025 +0100

    CAUSEWAY-3859: regression fix for composite value support
---
 .../commons/internal/delegate/_Delegate.java       |  11 +-
 .../facets/object/value/CompositeValueUpdater.java |  29 +++-
 .../facets/object/value/ValueFacetAbstract.java    |  10 +-
 .../interactions/managed/ActionInteraction.java    |   8 +-
 .../metamodel/spec/feature/HasObjectAction.java    | 180 +++++++++++++++++++++
 .../metamodel/spec/impl/ObjectActionDefault.java   |   2 +-
 .../interaction/act/ActionInteractionWkt.java      |  25 ++-
 7 files changed, 235 insertions(+), 30 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/delegate/_Delegate.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/delegate/_Delegate.java
index d6f017c4989..d31483d1c5b 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/delegate/_Delegate.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/delegate/_Delegate.java
@@ -29,7 +29,6 @@
 
 import org.apache.causeway.commons.internal.reflection._ClassCache;
 
-import lombok.RequiredArgsConstructor;
 import lombok.experimental.UtilityClass;
 
 /**
@@ -66,11 +65,9 @@ public <T> T createProxy(final Class<T> bluePrint, final 
Object delegate) {
                 new DelegatingInvocationHandler(delegate));
     }
 
-    @RequiredArgsConstructor
-    static class DelegatingInvocationHandler implements InvocationHandler {
-
-        final Object delegate;
-        final _ClassCache classCache = _ClassCache.getInstance();
+    record DelegatingInvocationHandler(
+        Object delegate)
+    implements InvocationHandler {
 
         @Override
         public Object invoke(final Object proxy, final Method method, final 
Object[] args)
@@ -90,7 +87,7 @@ public Object invoke(final Object proxy, final Method method, 
final Object[] arg
             // Invoke method with same signature on delegate ()
             try {
                 var delegateMethod =
-                        
classCache.lookupResolvedMethodElseFail(delegate.getClass(),
+                    
_ClassCache.getInstance().lookupResolvedMethodElseFail(delegate.getClass(),
                                 method.getName(), method.getParameterTypes());
                 return delegateMethod.method().invoke(delegate, args);
             } catch (InvocationTargetException ex) {
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/CompositeValueUpdater.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/CompositeValueUpdater.java
index 455bb347aee..4a44ea9096b 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/CompositeValueUpdater.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/CompositeValueUpdater.java
@@ -17,10 +17,15 @@
  *  under the License.
  */
 package org.apache.causeway.core.metamodel.facets.object.value;
+
+import org.apache.causeway.applib.annotation.PromptStyle;
+import org.apache.causeway.applib.annotation.Where;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.delegate._Delegate;
 import org.apache.causeway.core.metamodel.commons.CanonicalInvoker;
 import org.apache.causeway.core.metamodel.commons.ParameterConverters;
+import org.apache.causeway.core.metamodel.consent.Allow;
+import org.apache.causeway.core.metamodel.consent.Consent;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.facets.HasFacetedMethod;
 import 
org.apache.causeway.core.metamodel.facets.object.value.CompositeValueUpdater.CompositeValueUpdaterForParameter;
@@ -31,17 +36,35 @@
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.MmUnwrapUtils;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.HasObjectAction;
 import org.apache.causeway.core.metamodel.spec.feature.MixedInAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 
-sealed interface CompositeValueUpdater
+/**
+ * Implementations are proxied in support of composite value types.
+ * <p>
+ * @implSpec The proxy mimics an {@link ObjectAction},
+ *      hence extending {@link HasObjectAction} for delegation to {@link 
#mixedInAction()}.
+ */
+public sealed interface CompositeValueUpdater extends HasObjectAction
 permits CompositeValueUpdaterForProperty, CompositeValueUpdaterForParameter {
 
     MixedInAction mixedInAction();
     ObjectSpecification returnType();
     ManagedObject map(final ManagedObject valueType);
 
-    default ManagedObject execute(
+    // HasObjectAction
+    @Override default ObjectAction getObjectAction() { return mixedInAction(); 
}
+
+
+    // -- OBJECT ACTION MOCKUP
+
+    @Override default String getId() { return "proxyCompositeValueUpdater"; }
+    @Override default Consent isVisible(final ManagedObject a, final 
InteractionInitiatedBy b, final Where c) { return Allow.DEFAULT; }
+    @Override default Consent isUsable(final ManagedObject a, final 
InteractionInitiatedBy b, final Where c) { return Allow.DEFAULT; }
+    @Override default PromptStyle getPromptStyle() { return 
PromptStyle.INLINE_AS_IF_EDIT; }
+
+    @Override default ManagedObject execute(
             final InteractionHead head, final Can<ManagedObject> parameters,
             final InteractionInitiatedBy interactionInitiatedBy) {
         return map(simpleExecute(head, parameters));
@@ -64,7 +87,6 @@ public ManagedObject map(final ManagedObject newParamValue) {
             parameterNegotiationModel.setParamValue(paramIndex, newParamValue);
             return newParamValue;
         }
-
     }
 
     record CompositeValueUpdaterForProperty(
@@ -84,7 +106,6 @@ public ManagedObject map(final ManagedObject valueType) {
             propNeg.submit();
             return managedProperty.getOwner();
         }
-
     }
 
     // -- FACTORIES
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ValueFacetAbstract.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ValueFacetAbstract.java
index c56f432454a..779e1eb0af7 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ValueFacetAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/value/ValueFacetAbstract.java
@@ -349,11 +349,11 @@ private Predicate<ValueSemanticsProvider<T>> 
isMatchingAnyOf(final Can<String> q
      */
     protected Optional<ObjectAction> 
resolveCompositeValueMixinForFeature(final ObjectFeature feature) {
         return qualifiersAccepted(feature).add("default")
-        .stream()
-        .map(qualifier->feature.getElementType().getAction(qualifier, 
MixedIn.ONLY))
-        .filter(Optional::isPresent)
-        .map(Optional::get)
-        .findFirst();
+            .stream()
+            .map(qualifier->feature.getElementType().getAction(qualifier, 
MixedIn.ONLY))
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .findFirst();
     }
 
     @RequiredArgsConstructor
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteraction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteraction.java
index a4938aa28b9..ca83cca982f 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteraction.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ActionInteraction.java
@@ -63,14 +63,14 @@ public static interface ParameterInvalidCallback {
 
     // -- FACTORIES
 
-    public static final ActionInteraction start(
+    public static ActionInteraction start(
             final @NonNull ManagedObject owner,
             final @NonNull String memberId,
             final @NonNull Where where) {
         return startWithMultiselect(owner, memberId, where, Can::empty);
     }
 
-    public static final ActionInteraction startWithMultiselect(
+    public static ActionInteraction startWithMultiselect(
             final @NonNull ManagedObject owner,
             final @NonNull String actionId,
             final @NonNull Where where,
@@ -160,10 +160,6 @@ public static ActionInteraction startAsBoundToParameter(
             }
         }
 
-        //XXX[CAUSEWAY-3080] prior to this fix we returned... (which I'm not 
sure why - makes no sense to me)
-        //var paramValue = parameterNegotiationModel.getParamValue(paramIndex);
-        //return ActionInteraction.start(paramValue, memberId, where);
-
         // else if not a composite value
         return ActionInteraction.start(actionOwner, memberId, where);
     }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
new file mode 100644
index 00000000000..79636a8e75c
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/feature/HasObjectAction.java
@@ -0,0 +1,180 @@
+/*
+ *  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.causeway.core.metamodel.spec.feature;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.jspecify.annotations.NonNull;
+
+import org.apache.causeway.applib.annotation.SemanticsOf;
+import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.collections.CanVector;
+import org.apache.causeway.core.metamodel.consent.Consent;
+import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.causeway.core.metamodel.consent.InteractionResultSet;
+import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.facetapi.FeatureType;
+import org.apache.causeway.core.metamodel.interactions.InteractionHead;
+import 
org.apache.causeway.core.metamodel.interactions.managed.ActionInteractionHead;
+import org.apache.causeway.core.metamodel.object.ManagedObject;
+import org.apache.causeway.core.metamodel.spec.ActionScope;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+
+/**
+ * Introduced to allow for proxies of {@link ObjectAction}.
+ */
+@FunctionalInterface
+public interface HasObjectAction extends ObjectAction {
+
+    ObjectAction getObjectAction();
+
+    @Override default ObjectSpecification getDeclaringType() {
+        return getObjectAction().getDeclaringType();
+    }
+    @Override default String getHelp() {
+        return getObjectAction().getHelp();
+    }
+    @Override default boolean isAlwaysHidden() {
+        return getObjectAction().isAlwaysHidden();
+    }
+    @Override default Consent isVisible(final ManagedObject target, final 
InteractionInitiatedBy interactionInitiatedBy, final Where where) {
+        return getObjectAction().isVisible(target, interactionInitiatedBy, 
where);
+    }
+    @Override default Consent isUsable(final ManagedObject target, final 
InteractionInitiatedBy interactionInitiatedBy, final Where where) {
+        return getObjectAction().isUsable(target, interactionInitiatedBy, 
where);
+    }
+    @Override default boolean isPropertyOrCollection() {
+        return getObjectAction().isPropertyOrCollection();
+    }
+    @Override default boolean isOneToManyAssociation() {
+        return getObjectAction().isOneToManyAssociation();
+    }
+    @Override default boolean isOneToOneAssociation() {
+        return getObjectAction().isOneToOneAssociation();
+    }
+    @Override default boolean isAction() {
+        return getObjectAction().isAction();
+    }
+    @Override default boolean isExplicitlyAnnotated() {
+        return getObjectAction().isExplicitlyAnnotated();
+    }
+    @Override default String getId() {
+        return getObjectAction().getId();
+    }
+    @Override default String getFriendlyName(final Supplier<ManagedObject> 
domainObjectProvider) {
+        return getObjectAction().getFriendlyName(domainObjectProvider);
+    }
+    @Override default Optional<String> getStaticFriendlyName() {
+        return getObjectAction().getStaticFriendlyName();
+    }
+    @Override default String getCanonicalFriendlyName() {
+        return getObjectAction().getCanonicalFriendlyName();
+    }
+    @Override default Optional<String> getDescription(final 
Supplier<ManagedObject> domainObjectProvider) {
+        return getObjectAction().getDescription(domainObjectProvider);
+    }
+    @Override default Optional<String> getStaticDescription() {
+        return getObjectAction().getStaticDescription();
+    }
+    @Override default Optional<String> getCanonicalDescription() {
+        return getObjectAction().getCanonicalDescription();
+    }
+    @Override default ObjectSpecification getElementType() {
+        return getObjectAction().getElementType();
+    }
+    @Override default String asciiId() {
+        return getObjectAction().asciiId();
+    }
+    @Override default FeatureType getFeatureType() {
+        return getObjectAction().getFeatureType();
+    }
+    @Override
+    default FacetHolder getFacetHolder() {
+        return getObjectAction().getFacetHolder();
+    }
+    @Override default SemanticsOf getSemantics() {
+        return getObjectAction().getSemantics();
+    }
+    @Override default ActionScope getScope() {
+        return getObjectAction().getScope();
+    }
+    @Override default boolean isPrototype() {
+        return getObjectAction().isPrototype();
+    }
+    @Override default boolean isDeclaredOnMixin() {
+        return getObjectAction().isDeclaredOnMixin();
+    }
+    @Override default ObjectSpecification getReturnType() {
+        return getObjectAction().getReturnType();
+    }
+    @Override default boolean hasReturn() {
+        return getObjectAction().hasReturn();
+    }
+    @Override default ManagedObject executeWithRuleChecking(final 
InteractionHead head, final Can<ManagedObject> parameters,
+        final InteractionInitiatedBy interactionInitiatedBy, final Where 
where) throws AuthorizationException {
+        return getObjectAction().executeWithRuleChecking(head, parameters, 
interactionInitiatedBy, where);
+    }
+    @Override default ManagedObject execute(final InteractionHead head, final 
Can<ManagedObject> parameters,
+        final InteractionInitiatedBy interactionInitiatedBy) {
+        return getObjectAction().execute(head, parameters, 
interactionInitiatedBy);
+    }
+    @Override default Consent isArgumentSetValid(final InteractionHead head, 
final Can<ManagedObject> proposedArguments,
+        final InteractionInitiatedBy interactionInitiatedBy) {
+        return getObjectAction().isArgumentSetValid(head, proposedArguments, 
interactionInitiatedBy);
+    }
+    @Override default InteractionResultSet 
isArgumentSetValidForParameters(final InteractionHead head,
+        final Can<ManagedObject> proposedArguments, final 
InteractionInitiatedBy interactionInitiatedBy) {
+        return getObjectAction().isArgumentSetValidForParameters(head, 
proposedArguments, interactionInitiatedBy);
+    }
+    @Override default Consent isArgumentSetValidForAction(final 
InteractionHead head, final Can<ManagedObject> proposedArguments,
+        final InteractionInitiatedBy interactionInitiatedBy) {
+        return getObjectAction().isArgumentSetValidForAction(head, 
proposedArguments, interactionInitiatedBy);
+    }
+    @Override default ActionInteractionHead interactionHead(@NonNull final 
ManagedObject actionOwner) {
+        return getObjectAction().interactionHead(actionOwner);
+    }
+    @Override default int getParameterCount() {
+        return getObjectAction().getParameterCount();
+    }
+    @Override default Can<ObjectActionParameter> getParameters() {
+        return getObjectAction().getParameters();
+    }
+    @Override default Can<ObjectSpecification> getParameterTypes() {
+        return getObjectAction().getParameterTypes();
+    }
+    @Override default Can<ObjectActionParameter> getParameters(final 
Predicate<ObjectActionParameter> predicate) {
+        return getObjectAction().getParameters();
+    }
+    @Override default ObjectActionParameter getParameterById(final String 
paramId) {
+        return getObjectAction().getParameterById(paramId);
+    }
+    @Override default ObjectActionParameter getParameterByName(final String 
paramName) {
+        return getObjectAction().getParameterByName(paramName);
+    }
+    @Override default ManagedObject realTargetAdapter(final ManagedObject 
targetAdapter) {
+        return getObjectAction().realTargetAdapter(targetAdapter);
+    }
+    @Override default CanVector<ManagedObject> getChoices(final ManagedObject 
target, final InteractionInitiatedBy interactionInitiatedBy) {
+        return getObjectAction().getChoices(target, interactionInitiatedBy);
+    }
+
+}
\ No newline at end of file
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
index 375b6235a78..02f7681c796 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectActionDefault.java
@@ -70,7 +70,7 @@
 import lombok.extern.log4j.Log4j2;
 
 @Log4j2
- class ObjectActionDefault
+class ObjectActionDefault
 extends ObjectMemberAbstract
 implements ObjectAction, HasSpecificationLoaderInternal {
     private static final long serialVersionUID = 1L;
diff --git 
a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/interaction/act/ActionInteractionWkt.java
 
b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/interaction/act/ActionInteractionWkt.java
index 03581670d8c..e4b06783fbc 100644
--- 
a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/interaction/act/ActionInteractionWkt.java
+++ 
b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/interaction/act/ActionInteractionWkt.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.viewer.wicket.model.models.interaction.act;
 
+import java.lang.reflect.Proxy;
 import java.util.Optional;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -77,8 +78,11 @@ public class ActionInteractionWkt
      * we don't have to re-attach the entire model (ActionInteraction)
      * <p>
      * nullable in support of lazy evaluation
+     * <p>
+     * make sure we don't memoize non-serializable ObjectAction proxies (as 
introduced via composite value type support)
      */
     private @Nullable ObjectAction actionMemento;
+    private transient @Nullable ObjectAction objectAction; // might be a proxy 
(non-serializbale)
 
     private Can<UiParameterWkt> childModels;
     private @Nullable PropertyModel associatedWithPropertyIfAny;
@@ -125,13 +129,20 @@ private ActionInteractionWkt(
         super(bookmarkedObject);
         this.memberId = memberId;
         this.where = where;
-        this.actionMemento = objectAction
-                    .orElse(null);
+        setObjectAction(objectAction.orElse(null));
         this.associatedWithPropertyIfAny = associatedWithPropertyIfAny;
         this.associatedWithParameterIfAny = associatedWithParameterIfAny;
         this.associatedWithCollectionIfAny = associatedWithCollectionIfAny;
     }
 
+    private void setObjectAction(final ObjectAction objectAction) {
+        this.objectAction = objectAction;
+        this.actionMemento = objectAction!=null
+                && !Proxy.isProxyClass(objectAction.getClass())
+            ? objectAction
+            : null;
+    }
+
     @Override
     protected ActionInteraction load() {
 
@@ -162,13 +173,13 @@ public final ActionInteraction actionInteraction() {
 
     public final ObjectAction getMetaModel() {
         //[CAUSEWAY-3648] In support of the composite value type's 
'Xxx_default' mixin.
-        if(actionMemento==null) {
-            this.actionMemento = actionInteraction()
-                    .getObjectActionElseFail();
-        }
+        if(objectAction!=null) return objectAction;
+        if(actionMemento!=null) return actionMemento;
+        setObjectAction(actionInteraction()
+                .getObjectActionElseFail());
         // re-attachment fails, if the owner is not found (eg. deleted entity),
         // hence we return the directly memoized meta-model of the underlying 
action
-        return actionMemento;
+        return objectAction;
     }
 
     public Optional<PropertyModel> associatedWithProperty() {

Reply via email to