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/06/30 20:08:29 UTC
[isis] branch master updated: ISIS-1720: allow synthetic methods on
mixin types to be considered as action candidates
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 a6bfb6a ISIS-1720: allow synthetic methods on mixin types to be considered as action candidates
a6bfb6a is described below
commit a6bfb6ac3fe0b2fe7413bcdb3aaa1c6ce4773d1d
Author: andi-huber <ah...@apache.org>
AuthorDate: Wed Jun 30 22:07:50 2021 +0200
ISIS-1720: allow synthetic methods on mixin types to be considered as
action candidates
---
.../ignore/javalang/RemoveMethodsFacetFactory.java | 14 +-
.../specloader/specimpl/FacetedMethodsBuilder.java | 33 ++++-
.../specimpl/ObjectSpecificationAbstract.java | 29 ++--
.../interact/WrapperInteractionTest3.java | 157 +++++++++++++++++++++
4 files changed, 215 insertions(+), 18 deletions(-)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/ignore/javalang/RemoveMethodsFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/ignore/javalang/RemoveMethodsFacetFactory.java
index f32fd18..7edfe77 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/ignore/javalang/RemoveMethodsFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/ignore/javalang/RemoveMethodsFacetFactory.java
@@ -33,6 +33,7 @@ import org.apache.isis.core.metamodel.context.MetaModelContext;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import lombok.val;
@@ -77,15 +78,20 @@ public class RemoveMethodsFacetFactory extends FacetFactoryAbstract {
public void process(final ProcessClassContext processClassContext) {
super.process(processClassContext);
- Class<?> cls = processClassContext.getCls();
- Method[] methods = cls.getMethods();
+ val cls = processClassContext.getCls();
+ val facetHolder = processClassContext.getFacetHolder();
+ val isConcreteMixin = facetHolder instanceof ObjectSpecification
+ ? ((ObjectSpecification)facetHolder).getBeanSort().isMixin()
+ : false;
+ final Method[] methods = cls.getMethods();
val config = processClassContext.getFacetHolder().getMetaModelContext().getConfiguration();
val isExplicitAction = config.getApplib().getAnnotation().getAction().isExplicit();
for (Method method : methods) {
- // remove synthetic methods
- if (method.isSynthetic()) {
+ // remove synthetic methods (except when is a mixin)
+ if (!isConcreteMixin
+ && method.isSynthetic()) {
processClassContext.removeMethod(method);
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
index a7e3a4e..6a788dd 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
@@ -66,15 +66,27 @@ import lombok.extern.log4j.Log4j2;
public class FacetedMethodsBuilder
implements HasMetaModelContext {
+ private void debug(String format, Object...args) {
+ if(introspectedClass.getName().contains("WrapperInteractionTest3$Task$")) {
+ System.out.printf(format+"%n", args);
+ }
+ }
+
private static final String GET_PREFIX = "get";
private static final String IS_PREFIX = "is";
/* thread-safety ... make sure every methodsRemaining access is synchronized! */
- private static final class FacetedMethodsMethodRemover implements MethodRemover {
+ private static final class ConcurrentMethodRemover implements MethodRemover {
+
+ private void debug(String format, Method method) {
+ if(method.getDeclaringClass().getName().contains("WrapperInteractionTest3$Task$")) {
+ System.out.printf(format+"%n", method);
+ }
+ }
private final Set<Method> methodsRemaining;
- private FacetedMethodsMethodRemover(final Class<?> introspectedClass, final Method[] methods) {
+ private ConcurrentMethodRemover(final Class<?> introspectedClass, final Method[] methods) {
this.methodsRemaining = Stream.of(methods)
.filter(_NullSafe::isPresent)
.collect(Collectors.toCollection(_Sets::newConcurrentHashSet));
@@ -86,7 +98,9 @@ implements HasMetaModelContext {
val doRemove = removeIf.test(method);
if(doRemove) {
onRemoval.accept(method);
+ //debug("accept method-2 %s", method);
}
+ //debug("skipping method-2 %s", method);
return doRemove;
});
}
@@ -96,6 +110,10 @@ implements HasMetaModelContext {
if(method==null) {
return;
}
+ if(method.toString().contains("Succeeded.act(")) {
+ System.out.println("!!!gotcha " + method);
+ }
+ debug("remove method %s", method);
methodsRemaining.remove(method);
}
@@ -117,7 +135,7 @@ implements HasMetaModelContext {
private List<FacetedMethod> associationFacetMethods;
private List<FacetedMethod> actionFacetedMethods;
- private final FacetedMethodsMethodRemover methodRemover;
+ private final ConcurrentMethodRemover methodRemover;
@Getter private final FacetProcessor facetProcessor;
@@ -144,8 +162,12 @@ implements HasMetaModelContext {
this.explicitAnnotationsForActions =
getConfiguration().getApplib().getAnnotation().getAction().isExplicit();
+ if(introspectedClass.getName().endsWith("WrapperInteractionTest3$Task$Succeeded")) {
+ System.out.printf("introspectedClass[%s]: %s%n", introspectedClass, Can.ofArray(introspectedClass.getMethods()));
+ }
+
val methodsRemaining = introspectedClass.getMethods();
- this.methodRemover = new FacetedMethodsMethodRemover(introspectedClass, methodsRemaining);
+ this.methodRemover = new ConcurrentMethodRemover(introspectedClass, methodsRemaining);
}
@@ -367,6 +389,7 @@ implements HasMetaModelContext {
onActionFacetedMethod.accept(actionPeer);
return true;
}
+ debug("skipping method %s", method);
return false;
});
@@ -375,6 +398,8 @@ implements HasMetaModelContext {
private FacetedMethod findActionFacetedMethod(
final Method actionMethod) {
+ debug("represents action %s -> %b", actionMethod, representsAction(actionMethod));
+
if (!representsAction(actionMethod)) {
return null;
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index efbfbe7..08585e6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -272,6 +272,10 @@ implements ObjectSpecification {
log.debug("introspectingUpTo: {}, {}", getFullIdentifier(), upTo);
}
+ if(this.getCorrespondingClass().getName().endsWith("WrapperInteractionTest3$Task$MixinAbstract")) {
+ //System.out.printf("introspectUpTo[%s]: %s%n", this.getCorrespondingClass(), upTo);
+ }
+
boolean revalidate = false;
switch (introspectionState) {
@@ -769,11 +773,10 @@ implements ObjectSpecification {
// -- mixin actions
/**
- * All contributee actions (each wrapping a service's contributed action) for this spec.
- *
+ * All mixed in actions for this spec.
* <p>
- * If this specification {@link #isManagedBean() is actually for} a service,
- * then returns an empty list.
+ * If this specification represents a service ({@link #isManagedBean()} or a value or
+ * a mixin, then is a no-op.
*/
private void createMixedInActions(final Consumer<ObjectAction> onNewMixedInAction) {
if (isManagedBean() || isValue() || isMixin()) {
@@ -784,6 +787,12 @@ implements ObjectSpecification {
return;
}
for (val mixinType : mixinTypes) {
+
+ if(mixinType.getName().contains("WrapperInteractionTest")
+ && this.getCorrespondingClass().getName().endsWith("WrapperInteractionTest3$Task")) {
+ System.out.printf("mixinType[%s]: %s%n", this.getCorrespondingClass(), mixinType);
+ }
+
forEachMixedInAction(mixinType, onNewMixedInAction);
}
}
@@ -876,8 +885,8 @@ implements ObjectSpecification {
// -- GUARDS
- private boolean contributeeAndMixedInAssociationsAdded;
- private boolean contributeeAndMixedInActionsAdded;
+ private boolean mixedInAssociationsAdded;
+ private boolean mixedInActionsAdded;
private void createMixedInActions() {
// update our list of actions if requesting for contributed actions
@@ -885,13 +894,13 @@ implements ObjectSpecification {
// the "contributed.isIncluded()" guard is required because we cannot do this too early;
// there must be a session available
synchronized (unmodifiableActions) {
- if(!contributeeAndMixedInActionsAdded) {
+ if(!mixedInActionsAdded) {
val actions = _Lists.newArrayList(this.objectActions);
if (isEntityOrViewModelOrAbstract()) {
createMixedInActions(actions::add);
}
sortCacheAndUpdateActions(actions);
- contributeeAndMixedInActionsAdded = true;
+ mixedInActionsAdded = true;
}
}
}
@@ -900,13 +909,13 @@ implements ObjectSpecification {
// the "contributed.isIncluded()" guard is required because we cannot do this too early;
// there must be a session available
synchronized (unmodifiableAssociations) {
- if(!contributeeAndMixedInAssociationsAdded) {
+ if(!mixedInAssociationsAdded) {
val associations = _Lists.newArrayList(this.associations);
if(isEntityOrViewModelOrAbstract()) {
createMixedInAssociations(associations::add);
}
sortAndUpdateAssociations(associations);
- contributeeAndMixedInAssociationsAdded = true;
+ mixedInAssociationsAdded = true;
}
}
}
diff --git a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/WrapperInteractionTest3.java b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/WrapperInteractionTest3.java
new file mode 100644
index 0000000..b45571e
--- /dev/null
+++ b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/WrapperInteractionTest3.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.testdomain.interact;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Optionality;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.core.config.presets.IsisPresets;
+import org.apache.isis.core.metamodel.facets.all.named.MemberNamedFacet;
+import org.apache.isis.core.metamodel.spec.feature.MixedIn;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.testdomain.conf.Configuration_headless;
+import org.apache.isis.testdomain.model.interaction.Configuration_usingInteractionDomain;
+import org.apache.isis.testdomain.util.interaction.InteractionTestAbstract;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@SpringBootTest(
+ classes = {
+ Configuration_headless.class,
+ Configuration_usingInteractionDomain.class,
+
+ WrapperInteractionTest3.Task.class,
+ WrapperInteractionTest3.Task.Succeeded.class,
+ WrapperInteractionTest3.Task.Failed.class,
+ }
+)
+@TestPropertySource({
+ IsisPresets.SilenceMetaModel,
+ IsisPresets.SilenceProgrammingModel
+})
+class WrapperInteractionTest3
+extends InteractionTestAbstract {
+
+ @Data @DomainObject(nature = Nature.VIEW_MODEL)
+ public static class Task {
+
+ @RequiredArgsConstructor
+ enum Outcome {
+ SUPER(true),
+ GREAT(true),
+ OK(true),
+ BAD(false),
+ TERRIBLE(false),
+ JUST_GIVE_UP(false),;
+
+ final boolean representsSuccess;
+
+ public static List<Outcome> successes() {
+ return Arrays.stream(Outcome.values()).filter(x -> x.representsSuccess).collect(Collectors.toList());
+ }
+ public static List<Outcome> failures() {
+ return Arrays.stream(Outcome.values()).filter(x -> ! x.representsSuccess).collect(Collectors.toList());
+ }
+ }
+
+ @Property(optionality = Optionality.OPTIONAL)
+ Outcome outcome;
+
+ @Action
+ public class Succeeded
+ extends MixinAbstract {
+ public List<Task.Outcome> choices0Act() { return Task.Outcome.successes(); }
+ }
+
+ @Action
+ public class Failed
+ extends MixinAbstract {
+ public List<Task.Outcome> choices0Act() { return Task.Outcome.failures(); }
+ }
+
+ // an abstract mixin class
+ abstract class MixinAbstract {
+ public Task act(Task.Outcome outcome) {
+ Task.this.outcome = outcome;
+ return Task.this;
+ }
+ }
+ }
+
+ @Inject SpecificationLoader specificationLoader;
+
+ @Test
+ void mixinMemberNamedFacet_whenSharingSameAbstractMixin() {
+
+ val objectSpec1 = specificationLoader.specForType(Task.Succeeded.class).get();
+ val objectSpec2 = specificationLoader.specForType(Task.Failed.class).get();
+
+ assertTrue(objectSpec1.isMixin());
+ assertTrue(objectSpec2.isMixin());
+
+ assertEquals("Succeeded", objectSpec1.getSingularName());
+ assertEquals("Failed", objectSpec2.getSingularName());
+
+ val objectSpec = specificationLoader.specForType(Task.class).get();
+
+ assertEquals(
+ 2L,
+ objectSpec.streamRuntimeActions(MixedIn.INCLUDED)
+ //.filter(ObjectAction::isMixedIn)
+ .peek(act->{
+ System.out.println("act: " + act);
+ val memberNamedFacet = act.getFacet(MemberNamedFacet.class);
+ assertNotNull(memberNamedFacet);
+ assertTrue(memberNamedFacet.getSpecialization().isLeft());
+ })
+ .count());
+ }
+
+ @Test
+ void mixinActionValidation() {
+
+ final Task task = new Task();
+
+ wrapMixin(Task.Succeeded.class, task).act(Task.Outcome.SUPER);
+ Assertions.assertThat(task).extracting(Task::getOutcome).isEqualTo(Task.Outcome.SUPER);
+
+ wrapMixin(Task.Failed.class, task).act(Task.Outcome.JUST_GIVE_UP);
+ Assertions.assertThat(task).extracting(Task::getOutcome).isEqualTo(Task.Outcome.JUST_GIVE_UP);
+ }
+
+}