diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java
index 08c7cfe0f76..5490a6e4bc3 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/metamodel/BeanSort.java
@@ -20,7 +20,6 @@
import org.springframework.context.annotation.Profile;
-import org.apache.causeway.applib.annotation.Domain;
import org.apache.causeway.applib.annotation.Programmatic;
/**
@@ -59,8 +58,9 @@ public enum BeanSort {
*/
MANAGED_BEAN_NOT_CONTRIBUTING,
/**
- * Object associated with an 'entity' or 'bean' to act as contributer of
- * domain actions or properties. Might also be stateful similar to VIEW_MODEL.
+ * Object associated with an entity, viewmodel or domain-service
+ * to act as contributer of a single domain-action or
+ * domain-property or domain-collection.
*/
MIXIN,
/**
@@ -77,7 +77,7 @@ public enum BeanSort {
ABSTRACT,
/**
* Type must not be added to the meta-model, eg. by means of
- * {@link Domain#Exclude}, {@link Profile} or {@link Programmatic}
+ * {@link org.apache.causeway.applib.annotation.Domain.Exclude}, {@link Profile} or {@link Programmatic}
*/
VETOED,
UNKNOWN;
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
index 7d43a134d77..21962c7d060 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
@@ -537,6 +537,12 @@ public static enum Violation {
+ "'${memberId}', which is not allowed; clashes:\n\t[1]${member1}\n\t[2]${member2}"),
AMBIGUOUS_MIXIN_ANNOTATIONS("Annotation ${annot} on both method and type level is not allowed, "
+ "it must be one or the other. Found with mixin: ${mixinType}"),
+ INVALID_MIXIN_TYPE("Mixin ${type} could not be identified as action, property or collection."),
+ INVALID_MIXIN_MAIN("Mixin ${type} does declare method name '${expectedMethodName}' as"
+ + " the mixin main method to use,"
+ + " but introspection did pick up method '${actualMethodName}' instead."),
+ INVALID_MIXIN_SORT("Mixin ${type} is declared as contributing '${expectedContributing}'"
+ + " but introspection did pick it up as '${actualContributing}' instead."),
;
private final String template;
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacet.java
index 00db80f9f38..2482f2aaf32 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacet.java
@@ -19,43 +19,28 @@
package org.apache.causeway.core.metamodel.facets.actions.contributing;
import org.apache.causeway.core.metamodel.facetapi.Facet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
import lombok.val;
/**
* Indicates that the action should be contributed to objects either
- * as ACTION or ASSOCIATION.
+ * as Action, Property or Collection.
*
* Since v2 only ever used for mixed in actions.
* @since 2.0
*/
public interface ContributingFacet extends Facet {
- enum Contributing {
-
- /**
- * Contributed as an action but not as an association.
- */
- AS_ACTION,
-
- /**
- * (If takes a single argument and has safe semantics) then is contributed as an association
- * (contributed property if returns a single value, contributed collection if returns a collection) but not
- * as an action.
- */
- AS_ASSOCIATION,
- }
-
public Contributing contributed();
default boolean isActionContributionVetoed() {
- // not contributed to actions if...
- return contributed() == Contributing.AS_ASSOCIATION;
+ return contributed() == Contributing.AS_PROPERTY
+ || contributed() == Contributing.AS_COLLECTION;
}
default boolean isAssociationContributionVetoed() {
- // not contributed to associations if...
return contributed() == Contributing.AS_ACTION;
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacetAbstract.java
index f717726b0a5..127737c2eb6 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/ContributingFacetAbstract.java
@@ -23,33 +23,34 @@
import org.apache.causeway.core.metamodel.facetapi.Facet;
import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
+
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
public abstract class ContributingFacetAbstract
extends FacetAbstract
implements ContributingFacet {
- private final Contributing contributing;
+ @Getter(onMethod_={@Override}) @Accessors(fluent=true)
+ private final @NonNull Contributing contributed;
private static final Class extends Facet> type() {
return ContributingFacet.class;
}
public ContributingFacetAbstract(
- final Contributing contributing,
+ final Contributing contributed,
final FacetHolder holder) {
super(type(), holder);
- this.contributing = contributing;
- }
-
- @Override
- public Contributing contributed() {
- return contributing;
+ this.contributed = contributed;
}
@Override
public void visitAttributes(final BiConsumer visitor) {
super.visitAttributes(visitor);
- visitor.accept("contributing", contributing);
+ visitor.accept("contributing", contributed());
}
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/derived/ContributingFacetFromMixinFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/derived/ContributingFacetFromMixinFacetFactory.java
index 45add474d40..c03b88f84d7 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/derived/ContributingFacetFromMixinFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/contributing/derived/ContributingFacetFromMixinFacetFactory.java
@@ -26,9 +26,9 @@
import org.apache.causeway.core.metamodel.facetapi.FacetUtil;
import org.apache.causeway.core.metamodel.facetapi.FeatureType;
import org.apache.causeway.core.metamodel.facets.FacetFactoryAbstract;
-import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacetAbstract;
import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import lombok.val;
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
index ef6b82a4f55..e570f3c8256 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
@@ -28,13 +28,14 @@
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facetapi.FeatureType;
import org.apache.causeway.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.causeway.core.metamodel.facets.FacetedMethod;
import org.apache.causeway.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
-import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacetAbstract;
import org.apache.causeway.core.metamodel.facets.actions.semantics.ActionSemanticsFacetAbstract;
import org.apache.causeway.core.metamodel.facets.collections.collection.hidden.HiddenFacetForCollectionAnnotation;
import org.apache.causeway.core.metamodel.facets.collections.collection.modify.CollectionDomainEventFacet;
import org.apache.causeway.core.metamodel.facets.collections.collection.typeof.TypeOfFacetForCollectionAnnotation;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailureUtils;
@@ -53,7 +54,11 @@ public void process(final ProcessMethodContext processMethodContext) {
val collectionIfAny = collectionIfAny(processMethodContext);
- inferIntentWhenOnTypeLevel(processMethodContext, collectionIfAny);
+ if(processMethodContext.isMixinMain()) {
+ collectionIfAny.ifPresent(collection->{
+ inferMixinSort(collection, processMethodContext.getFacetHolder());
+ });
+ }
processDomainEvent(processMethodContext, collectionIfAny);
processHidden(processMethodContext, collectionIfAny);
@@ -68,22 +73,11 @@ Optional collectionIfAny(final ProcessMethodContext processMethodCon
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), Collection.class));
}
- void inferIntentWhenOnTypeLevel(final ProcessMethodContext processMethodContext, final Optional collectionIfAny) {
- if(!processMethodContext.isMixinMain() || !collectionIfAny.isPresent()) {
- return; // no @Collection found neither type nor method
- }
-
- // XXX[1998] this condition would allow 'intent inference' only when @Property is found at type level
- // val isPropertyMethodLevel = processMethodContext.synthesizeOnMethod(Property.class).isPresent();
- // if(isPropertyMethodLevel) return;
-
- //[1998] if @Collection detected on method or type level infer:
- //@Action(semantics=SAFE)
- //@ActionLayout(contributed=ASSOCIATION) ... it seems, is already allowed for mixins
- val facetedMethod = processMethodContext.getFacetHolder();
+ void inferMixinSort(final Collection collection, final FacetedMethod facetedMethod) {
+ /* if @Collection detected on method or type level infer:
+ * @Action(semantics=SAFE) */
addFacet(new ActionSemanticsFacetAbstract(SemanticsOf.SAFE, facetedMethod) {});
- addFacet(new ContributingFacetAbstract(Contributing.AS_ASSOCIATION, facetedMethod) {});
-
+ addFacet(new ContributingFacetAbstract(Contributing.AS_COLLECTION, facetedMethod) {});
}
void processDomainEvent(final ProcessMethodContext processMethodContext, final Optional collectionIfAny) {
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacet.java
index 50fc64d3649..1b93f6962e1 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacet.java
@@ -25,7 +25,7 @@
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.Nature;
import org.apache.causeway.applib.annotation.Property;
-import org.apache.causeway.core.metamodel.facets.SingleValueFacet;
+import org.apache.causeway.core.metamodel.facetapi.Facet;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
/**
@@ -40,7 +40,38 @@
* the type recognized as a mix-in. These are {@link Action}, {@link Property} and
* {@link Collection}.
*/
-public interface MixinFacet extends SingleValueFacet {
+public interface MixinFacet extends Facet {
+
+ public enum Contributing {
+ /**
+ * Initial state early during introspection.
+ */
+ UNSPECIFIED,
+ /**
+ * Object associated with an entity, viewmodel or domain-service
+ * to act as contributer of a single domain-action.
+ */
+ AS_ACTION,
+ /**
+ * Object associated with an entity, viewmodel or domain-service
+ * to act as contributer of a single domain-property.
+ */
+ AS_PROPERTY,
+ /**
+ * Object associated with an entity, viewmodel or domain-service
+ * to act as contributer of a single domain-collection.
+ */
+ AS_COLLECTION;
+
+ public boolean isUnspecified() { return this==UNSPECIFIED; }
+ }
+
+ Contributing contributing();
+
+ /**
+ * The mixin's main method name.
+ */
+ String getMainMethodName();
boolean isMixinFor(Class> candidateDomainType);
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetAbstract.java
index 26f06d0acd1..d481bdbbc57 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetAbstract.java
@@ -25,17 +25,26 @@
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.metamodel.facetapi.Facet;
+import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
-import org.apache.causeway.core.metamodel.facets.SingleValueFacetAbstract;
+import org.apache.causeway.core.metamodel.facets.FacetedMethod;
+import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacet;
+import lombok.Getter;
import lombok.NonNull;
import lombok.val;
+import lombok.experimental.Accessors;
//@Log4j2
public abstract class MixinFacetAbstract
-extends SingleValueFacetAbstract
+extends FacetAbstract
implements MixinFacet {
+ @Getter(onMethod_={@Override})
+ private final @NonNull String mainMethodName;
+ @Getter(onMethod_={@Override}) @Accessors(fluent=true)
+ private @NonNull Contributing contributing = Contributing.UNSPECIFIED;
+
private final @NonNull Class> mixinType;
private final @NonNull Class> holderType;
private final @NonNull Constructor> constructor;
@@ -50,7 +59,8 @@ protected MixinFacetAbstract(
final Constructor> constructor,
final FacetHolder holder) {
- super(type(), mainMethodName, holder);
+ super(type(), holder);
+ this.mainMethodName = mainMethodName;
this.mixinType = mixinType;
this.constructor = constructor;
// by mixin convention: first constructor argument is identified as the holder type
@@ -59,11 +69,9 @@ protected MixinFacetAbstract(
@Override
public boolean isMixinFor(final Class> candidateDomainType) {
- if (candidateDomainType == null) {
- return false;
- }
-
- return holderType.isAssignableFrom(candidateDomainType);
+ return candidateDomainType == null
+ ? false
+ : holderType.isAssignableFrom(candidateDomainType);
}
@Override
@@ -97,13 +105,12 @@ public Object instantiate(final Object mixee) {
@Override
public boolean isCandidateForMain(final Method method) {
-
- // include methods from super classes or interfaces
- //
- // it is sufficient to detect any match;
- // mixin invocation will take care of calling the right method,
- // that is in terms of type-hierarchy the 'nearest' to this mixin
-
+ /* include methods from super classes or interfaces
+ *
+ * it is sufficient to detect any match;
+ * mixin invocation will take care of calling the right method,
+ * that is in terms of type-hierarchy the 'nearest' to this mixin;
+ */
return method.getName().equals(getMainMethodName())
&& method.getDeclaringClass()
.isAssignableFrom(constructor.getDeclaringClass());
@@ -113,15 +120,22 @@ public boolean isCandidateForMain(final Method method) {
public void visitAttributes(final BiConsumer visitor) {
super.visitAttributes(visitor);
visitor.accept("mixinType", mixinType);
+ visitor.accept("contributing", contributing);
+ visitor.accept("mainMethodName", mainMethodName);
visitor.accept("holderType", holderType);
}
/**
- * The mixin's main method name.
- * @implNote as stored in the SingleValueFacetAbstract's value field
+ * Framework internal: copy the mixin-sort ({@link MixinFacet.Contributing})
+ * information from the {@link FacetedMethod}
+ * (as eg. associated with mixin main method 'act')
+ * to the {@link MixinFacet} that is held by the mixin's type spec.
*/
- public String getMainMethodName() {
- return super.value();
+ public void initMixinSortFrom(final FacetedMethod facetedMethod) {
+ this.contributing = facetedMethod
+ .lookupFacet(ContributingFacet.class)
+ .map(ContributingFacet::contributed)
+ .orElse(Contributing.AS_ACTION); // if not specified, defaults to ACTION
}
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
index 6578a62ce83..081f58ff9ec 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
@@ -62,4 +62,5 @@ private MixinFacetForDomainObjectAnnotation(
final FacetHolder holder) {
super(mixinClass, mixinMethodName, constructor, holder);
}
+
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index 2864b238cda..bed15f00d33 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -29,11 +29,12 @@
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facetapi.FeatureType;
import org.apache.causeway.core.metamodel.facets.FacetFactoryAbstract;
-import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacet.Contributing;
+import org.apache.causeway.core.metamodel.facets.FacetedMethod;
import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacetAbstract;
import org.apache.causeway.core.metamodel.facets.actions.semantics.ActionSemanticsFacetAbstract;
import org.apache.causeway.core.metamodel.facets.members.publish.command.CommandPublishingFacetForPropertyAnnotation;
import org.apache.causeway.core.metamodel.facets.members.publish.execution.ExecutionPublishingPropertyFacetForPropertyAnnotation;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
import org.apache.causeway.core.metamodel.facets.properties.projection.ProjectingFacetFromPropertyAnnotation;
import org.apache.causeway.core.metamodel.facets.properties.property.disabled.DisabledFacetForPropertyAnnotation;
@@ -69,7 +70,11 @@ public void process(final ProcessMethodContext processMethodContext) {
val propertyIfAny = propertyIfAny(processMethodContext);
- inferIntentWhenOnTypeLevel(processMethodContext, propertyIfAny);
+ if(processMethodContext.isMixinMain()) {
+ propertyIfAny.ifPresent(property->{
+ inferMixinSort(property, processMethodContext.getFacetHolder());
+ });
+ }
processDomainEvent(processMethodContext, propertyIfAny);
processHidden(processMethodContext, propertyIfAny);
@@ -94,21 +99,11 @@ Optional propertyIfAny(final ProcessMethodContext processMethodContext
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), Property.class));
}
- void inferIntentWhenOnTypeLevel(final ProcessMethodContext processMethodContext, final Optional propertyIfAny) {
- if(!processMethodContext.isMixinMain() || !propertyIfAny.isPresent()) {
- return; // no @Property found neither type nor method
- }
-
- // XXX[1998] this condition would allow 'intent inference' only when @Property is found at type level
- // val isPropertyMethodLevel = processMethodContext.synthesizeOnMethod(Property.class).isPresent();
- // if(isPropertyMethodLevel) return;
-
- //[1998] if @Property detected on method or type level infer:
- //@Action(semantics=SAFE)
- //@ActionLayout(contributed=ASSOCIATION) ... it seems, is already allowed for mixins
- val facetedMethod = processMethodContext.getFacetHolder();
+ void inferMixinSort(final Property property, final FacetedMethod facetedMethod) {
+ /* if @Property detected on method or type level infer:
+ * @Action(semantics=SAFE) */
addFacet(new ActionSemanticsFacetAbstract(SemanticsOf.SAFE, facetedMethod) {});
- addFacet(new ContributingFacetAbstract(Contributing.AS_ASSOCIATION, facetedMethod) {});
+ addFacet(new ContributingFacetAbstract(Contributing.AS_PROPERTY, facetedMethod) {});
}
void processDomainEvent(final ProcessMethodContext processMethodContext, final Optional propertyIfAny) {
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/all/MixinSanityChecksValidator.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/all/MixinSanityChecksValidator.java
new file mode 100644
index 00000000000..07d0f7062fc
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/postprocessors/all/MixinSanityChecksValidator.java
@@ -0,0 +1,132 @@
+/*
+ * 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.postprocessors.all;
+
+import javax.inject.Inject;
+
+import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.Collection;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.services.metamodel.BeanSort;
+import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants;
+import org.apache.causeway.core.metamodel.context.MetaModelContext;
+import org.apache.causeway.core.metamodel.facets.FacetedMethod;
+import org.apache.causeway.core.metamodel.facets.actions.contributing.ContributingFacet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract;
+import org.apache.causeway.core.metamodel.specloader.validator.MetaModelValidator;
+import org.apache.causeway.core.metamodel.specloader.validator.MetaModelValidatorAbstract;
+import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailure;
+
+import lombok.val;
+
+/**
+ * Checks various preconditions for a sane meta-model.
+ *
+ * - Guard against types that are identified as {@link BeanSort#MIXIN}, but don't contribute any member.
+ * - Make sure mixins contribute either an action or a property or a collection,
+ * and if declared via {@link Action}, {@link Property} or {@link Collection} annotation,
+ * that the {@link Contributing} is correct.
+ * - Make sure if a mixin main method name is specified (eg. 'act', 'prop', 'coll'),
+ * that introspection was able to pick it up.
+ *
+ */
+public class MixinSanityChecksValidator
+extends MetaModelValidatorAbstract
+implements
+ MetaModelValidator.ActionValidator {
+
+ private Contributing contributing;
+
+ @Inject
+ public MixinSanityChecksValidator(final MetaModelContext mmc) {
+ super(mmc, MIXINS);
+ }
+
+ @Override
+ public void validateObjectEnter(final ObjectSpecification objSpec) {
+ final Contributing contributing = objSpec.contributing().orElse(null);
+ if(contributing==null
+ || contributing.isUnspecified()) {
+ ValidationFailure.raiseFormatted(objSpec,
+ ProgrammingModelConstants.Violation.INVALID_MIXIN_TYPE
+ .builder()
+ .addVariable("type", objSpec.getCorrespondingClass().getName())
+ .buildMessage());
+ return;
+ }
+ this.contributing = contributing;
+ }
+
+ @Override
+ public void validateObjectExit(final ObjectSpecification objSpec) {
+ this.contributing = null;
+ }
+
+ /*
+ * (introspected) mixins have no properties nor collections; instead the single member is always
+ * an action that either contributes as action, property or collection
+ */
+ @Override
+ public void validateAction(final ObjectSpecification objSpec, final ObjectAction act) {
+ if(contributing==null) return; // skip if already failed earlier
+ checkMixinMainMethod(objSpec, act.getFeatureIdentifier());
+ checkMixinSort(objSpec, (FacetedMethod) act.getFacetHolder());
+ }
+
+ // -- HELPER
+
+ private void checkMixinSort(final ObjectSpecification objSpec, final FacetedMethod facetedMethod) {
+ val expectedContributing = facetedMethod.lookupFacet(ContributingFacet.class)
+ .map(ContributingFacet::contributed)
+ .orElse(Contributing.AS_ACTION); // if not specified, defaults to action
+ val actualContributing = this.contributing;
+
+ if(actualContributing!=expectedContributing) {
+ ValidationFailure.raiseFormatted(objSpec,
+ ProgrammingModelConstants.Violation.INVALID_MIXIN_SORT
+ .builder()
+ .addVariable("type", objSpec.getCorrespondingClass().getName())
+ .addVariable("expectedContributing", expectedContributing.name())
+ .addVariable("actualContributing", actualContributing.name())
+ .buildMessage());
+ }
+ }
+
+ private void checkMixinMainMethod(final ObjectSpecification objSpec, final Identifier memberIdentifier) {
+ val mixinFacet = ((ObjectSpecificationAbstract)objSpec).mixinFacet().orElseThrow();
+
+ val expectedMethodName = mixinFacet.getMainMethodName();
+ val actualMethodName = memberIdentifier.getMemberLogicalName();
+
+ if(!expectedMethodName.equals(actualMethodName)) {
+ ValidationFailure.raiseFormatted(objSpec,
+ ProgrammingModelConstants.Violation.INVALID_MIXIN_MAIN
+ .builder()
+ .addVariable("type", objSpec.getCorrespondingClass().getName())
+ .addVariable("expectedMethodName", expectedMethodName)
+ .addVariable("actualMethodName", actualMethodName)
+ .buildMessage());
+ }
+ }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodels/dflt/ProgrammingModelFacetsJava11.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodels/dflt/ProgrammingModelFacetsJava11.java
index 4c4028518fc..07b3561412c 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodels/dflt/ProgrammingModelFacetsJava11.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/progmodels/dflt/ProgrammingModelFacetsJava11.java
@@ -86,6 +86,7 @@
import org.apache.causeway.core.metamodel.postprocessors.all.CssOnActionFromConfiguredRegexPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.DescribedAsFromTypePostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.SanityChecksValidator;
+import org.apache.causeway.core.metamodel.postprocessors.all.MixinSanityChecksValidator;
import org.apache.causeway.core.metamodel.postprocessors.all.i18n.SynthesizeObjectNamingPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.all.i18n.TranslationPostProcessor;
import org.apache.causeway.core.metamodel.postprocessors.allbutparam.authorization.AuthorizationPostProcessor;
@@ -272,6 +273,8 @@ private void addValidators() {
addValidator(ValidationOrder.A1_BUILTIN, new ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator(mmc));
addValidator(ValidationOrder.A1_BUILTIN, new ActionOverloadingValidator(mmc));
addValidator(ValidationOrder.A1_BUILTIN, new LogicalTypeMalformedValidator(mmc));
+
+ addValidator(ValidationOrder.A1_BUILTIN, new MixinSanityChecksValidator(mmc));
}
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
index ea564878f30..d3b415e5f64 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java
@@ -59,6 +59,7 @@
import org.apache.causeway.core.metamodel.facets.object.icon.IconFacet;
import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon;
import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.object.parented.ParentedCollectionFacet;
import org.apache.causeway.core.metamodel.facets.object.title.TitleFacet;
import org.apache.causeway.core.metamodel.facets.object.title.TitleRenderRequest;
@@ -285,11 +286,18 @@ public default Optional lookupMixedInAction(final ObjectSpe
Optional getElementSpecification();
/**
- *
* @since 2.0
*/
BeanSort getBeanSort();
+ /**
+ * Optionally the mixin sort {@link Contributing},
+ * based on whether the corresponding class is a mixin type.
+ * @since 2.0
+ */
+ Optional contributing();
+
+
// //////////////////////////////////////////////////////////////
// TitleContext
// //////////////////////////////////////////////////////////////
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index e19a25dc6e1..0bf5b9c88b8 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -67,6 +67,7 @@
import org.apache.causeway.core.metamodel.facets.object.immutable.ImmutableFacet;
import org.apache.causeway.core.metamodel.facets.object.logicaltype.AliasedFacet;
import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet.Contributing;
import org.apache.causeway.core.metamodel.facets.object.navparent.NavigableParentFacet;
import org.apache.causeway.core.metamodel.facets.object.parented.ParentedCollectionFacet;
import org.apache.causeway.core.metamodel.facets.object.title.TitleFacet;
@@ -90,9 +91,11 @@
import static org.apache.causeway.commons.internal.base._NullSafe.stream;
+import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.val;
+import lombok.experimental.Accessors;
import lombok.extern.log4j.Log4j2;
@EqualsAndHashCode(of = "correspondingClass", callSuper = false)
@@ -533,6 +536,11 @@ public String getHelp() {
return helpFacet == null ? null : helpFacet.value();
}
+ @Override
+ public final Optional contributing() {
+ return mixinFacet()
+ .map(MixinFacet::contributing);
+ }
// -- FACET HANDLING
@@ -723,22 +731,22 @@ private Stream createMixedInAssociations() {
private Stream createMixedInAssociation(final Class> mixinType) {
- val specification = getSpecificationLoader().loadSpecification(mixinType,
+ val mixinSpec = getSpecificationLoader().loadSpecification(mixinType,
IntrospectionState.FULLY_INTROSPECTED);
- if (specification == this) {
+ if (mixinSpec == this) {
return Stream.empty();
}
- val mixinFacet = specification.getFacet(MixinFacet.class);
+ val mixinFacet = mixinSpec.getFacet(MixinFacet.class);
if(mixinFacet == null) {
- // this shouldn't happen; perhaps it would be more correct to throw an exception?
+ // this shouldn't happen; to be covered by meta-model validation later
return Stream.empty();
}
if(!mixinFacet.isMixinFor(getCorrespondingClass())) {
return Stream.empty();
}
- val mixinMethodName = mixinFacet.value();
+ val mixinMethodName = mixinFacet.getMainMethodName();
- return specification.streamActions(ActionScope.ANY, MixedIn.INCLUDED)
+ return mixinSpec.streamActions(ActionScope.ANY, MixedIn.EXCLUDED)
.filter(_SpecPredicates::isMixedInAssociation)
.map(ObjectActionDefault.class::cast)
.map(_MixedInMemberFactory.mixedInAssociation(this, mixinType, mixinMethodName))
@@ -763,7 +771,7 @@ private Stream createMixedInAction(final Class> mixinType
}
val mixinFacet = mixinSpec.getFacet(MixinFacet.class);
if(mixinFacet == null) {
- // this shouldn't happen; perhaps it would be more correct to throw an exception?
+ // this shouldn't happen; to be covered by meta-model validation later
return Stream.empty();
}
if(!mixinFacet.isMixinFor(getCorrespondingClass())) {
@@ -775,7 +783,7 @@ private Stream createMixedInAction(final Class> mixinType
return Stream.empty();
}
- val mixinMethodName = mixinFacet.value();
+ val mixinMethodName = mixinFacet.getMainMethodName();
return mixinSpec.streamActions(ActionScope.ANY, MixedIn.EXCLUDED)
// value types only support constructor mixins
@@ -899,6 +907,22 @@ private void createMixedInAssociationsAndResort() {
mixedInAssociations.stream()));
}
+ // -- MIXIN FACET LOOKUP - WITH CACHING
+
+ @Getter(lazy = true, value = AccessLevel.PUBLIC) @Accessors(fluent=true)
+ private final Optional mixinFacet = lookupMixinFacet();
+ private Optional lookupMixinFacet() {
+ if(!isMixin()) {
+ return Optional.empty();
+ }
+ val mixinFacet = getFacet(MixinFacet.class);
+ if(mixinFacet==null) {
+ throw _Exceptions.illegalState("type %s has BeanSort MIXIN but ended up NOT having a MixinFacet",
+ getCorrespondingClass());
+ }
+ return Optional.of(mixinFacet);
+ }
+
@Getter(lazy = true)
private final CausewayBeanTypeRegistry causewayBeanTypeRegistry =
getServiceRegistry()
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index b11227a75a1..d8866d7cd41 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -32,6 +32,7 @@
import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.collections.ImmutableEnumSet;
+import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._Strings;
@@ -49,6 +50,7 @@
import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacet;
import org.apache.causeway.core.metamodel.facets.all.named.MemberNamedFacetForStaticMemberName;
import org.apache.causeway.core.metamodel.facets.object.introspection.IntrospectionPolicyFacet;
+import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacetAbstract;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorRegistry;
import org.apache.causeway.core.metamodel.spec.ActionScope;
@@ -221,6 +223,14 @@ private Stream createActions() {
private ObjectAction createAction(final FacetedMethod facetedMethod) {
if (facetedMethod.getFeatureType().isAction()) {
+ /* Assuming, that facetedMethod was already populated with ContributingFacet,
+ * we copy the mixin-sort information from the FacetedMethod to the MixinFacet
+ * that is held by the mixin's type spec. */
+ mixinFacet()
+ .flatMap(mixinFacet->_Casts.castTo(MixinFacetAbstract.class, mixinFacet))
+ .ifPresent(mixinFacetAbstract->
+ mixinFacetAbstract.initMixinSortFrom(facetedMethod));
+
return this.isMixin()
? ObjectActionDefault.forMixinMain(facetedMethod)
: ObjectActionDefault.forMethod(facetedMethod);
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/MetaModelValidator.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/MetaModelValidator.java
index 34a4360a2e4..93588249562 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/MetaModelValidator.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/MetaModelValidator.java
@@ -99,10 +99,14 @@ static interface CollectionValidator {
public final static Predicate SKIP_MANAGED_BEANS =
spec->!spec.isInjectable();
- /** types pass this filter, if not a mixin */
+ /** types pass this filter, if is NOT a mixin */
public final static Predicate SKIP_MIXINS =
spec->!spec.isMixin();
+ /** types pass this filter, if IS a mixin */
+ public final static Predicate MIXINS =
+ spec->spec.isMixin();
+
/** types pass this filter, if either not {@link ObjectSpecificationAbstract} or member-annotation is not required */
public final static Predicate SKIP_WHEN_MEMBER_ANNOT_REQUIRED =
spec->(!(spec instanceof ObjectSpecificationAbstract)
diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml
index c52610eed40..46d6d3a7e90 100644
--- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml
+++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomainWithPdfJsViewer_IntegTest.dump_facets.approved.xml
@@ -128,7 +128,7 @@
-
+
@@ -1019,11 +1019,12 @@
+
+
-
@@ -1080,7 +1081,7 @@
-
+
diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml
index 80beffd31c8..9b9b3792811 100644
--- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml
+++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_MixinDomain_IntegTest.dump_facets.approved.xml
@@ -128,7 +128,7 @@
-
+
@@ -1012,11 +1012,12 @@
+
+
-
@@ -1073,7 +1074,7 @@
-
+
diff --git a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain.java b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
index 3e781a7c4ed..ec157f0a143 100644
--- a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
+++ b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
@@ -65,6 +65,7 @@
import org.apache.causeway.testdomain.model.bad.InvalidElementTypes;
import org.apache.causeway.testdomain.model.bad.InvalidMemberIdClash;
import org.apache.causeway.testdomain.model.bad.InvalidMemberOverloadingWhenInherited;
+import org.apache.causeway.testdomain.model.bad.InvalidMixinDeclarations;
import org.apache.causeway.testdomain.model.bad.InvalidObjectWithAlias;
import org.apache.causeway.testdomain.model.bad.InvalidOrphanedActionSupport;
import org.apache.causeway.testdomain.model.bad.InvalidOrphanedCollectionSupport;
@@ -126,7 +127,7 @@ void ambiguousTitle_shouldFail() {
void orphanedActionSupport_shouldFail() {
validator.assertAnyFailuresContaining(
Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedActionSupport.class)),
- validationMessage(
+ unsatisfiedDomainIncludeSemantics(
"InvalidOrphanedActionSupport",
"hideOrphaned()"));
@@ -141,7 +142,7 @@ void orphanedActionSupport_shouldFail() {
void orphanedPropertySupport_shouldFail() {
validator.assertAnyFailuresContaining(
Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedPropertySupport.class)),
- validationMessage(
+ unsatisfiedDomainIncludeSemantics(
"InvalidOrphanedPropertySupport",
"hideMyProperty()"));
@@ -155,7 +156,7 @@ void orphanedPropertySupport_shouldFail() {
void orphanedCollectionSupport_shouldFail() {
validator.assertAnyFailuresContaining(
Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedCollectionSupport.class)),
- validationMessage(
+ unsatisfiedDomainIncludeSemantics(
"InvalidOrphanedCollectionSupport",
"hideMyCollection()"));
@@ -208,14 +209,14 @@ void actionOverloading_shouldFail() {
validator.assertAnyFailuresContaining(
Identifier.classIdentifier(LogicalType.fqcn(
InvalidMemberOverloadingWhenInherited.WhenAnnotationRequired.class)),
- validationMessage(
+ unsatisfiedDomainIncludeSemantics(
"",
"isActive()"));
validator.assertAnyFailuresContaining(
Identifier.classIdentifier(LogicalType.fqcn(
InvalidMemberOverloadingWhenInherited.WhenEncapsulationEnabled.class)),
- validationMessage(
+ unsatisfiedDomainIncludeSemantics(
"",
"isActive()"));
}
@@ -405,6 +406,34 @@ void invalidElementType(final Class> classUnderTest) {
"has a member with vetoed, mixin or managed element-type");
}
+ // -- MIXINS
+
+ @ParameterizedTest
+ @ValueSource(classes = {
+ InvalidMixinDeclarations.ActionMixinWithProp.class,
+ InvalidMixinDeclarations.ActionMixinWithColl.class,
+ InvalidMixinDeclarations.PropertyMixinWithOther.class,
+ InvalidMixinDeclarations.CollectionMixinWithOther.class,
+ })
+ void invalidMixinDeclaration(final Class> classUnderTest) {
+
+ // just by convention of these test scenarios ...
+ final String expectedMethodName = classUnderTest.getSimpleName().startsWith("Property")
+ ? "prop"
+ : classUnderTest.getSimpleName().startsWith("Collection")
+ ? "coll"
+ : "act";
+
+ validator.assertAnyFailuresContaining(
+ classUnderTest,
+ ProgrammingModelConstants.Violation.INVALID_MIXIN_MAIN.builder()
+ .addVariable("type", classUnderTest.getName())
+ .addVariable("expectedMethodName", expectedMethodName)
+ .addVariable("actualMethodName", "other")
+ .buildMessage()
+ );
+ }
+
// -- INCUBATING
@Test @Disabled("this case has no vaildation refiner yet")
@@ -427,7 +456,7 @@ void invalidPropertyAnnotationOnAction_shouldFail() {
// -- HELPER
- private String validationMessage(
+ private String unsatisfiedDomainIncludeSemantics(
final String className,
final String memberName) {
return Violation.UNSATISFIED_DOMAIN_INCLUDE_SEMANTICS
diff --git a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml
index 6c1e39ae9b4..7284cfae99e 100644
--- a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml
+++ b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/MetaModelRegressionTest.verify.approved.xml
@@ -54,11 +54,12 @@
+
+
-
@@ -2761,11 +2762,12 @@
+
+
-
@@ -3879,11 +3881,12 @@
+
+
-
@@ -14212,7 +14215,7 @@
-
+
@@ -14352,7 +14355,7 @@
-
+
@@ -14492,7 +14495,7 @@
-
+
@@ -14606,7 +14609,7 @@
-
+
@@ -14737,7 +14740,7 @@
-
+
@@ -14868,7 +14871,7 @@
-
+
@@ -20915,11 +20918,12 @@
+
+
-
@@ -21238,11 +21242,12 @@
+
+
-
@@ -21550,11 +21555,12 @@
+
+
-
@@ -21876,11 +21882,12 @@
+
+
-
@@ -22202,11 +22209,12 @@
+
+
-
@@ -22387,11 +22395,12 @@
+
+
-
@@ -22701,11 +22710,12 @@
+
+
-
@@ -22765,7 +22775,7 @@
-
+
@@ -22924,11 +22934,12 @@
+
+
-
@@ -22988,7 +22999,7 @@
-
+
@@ -23147,11 +23158,12 @@
+
+
-
@@ -23211,7 +23223,7 @@
-
+
@@ -23349,11 +23361,12 @@
+
+
-
@@ -23406,7 +23419,7 @@
-
+
@@ -23581,11 +23594,12 @@
+
+
-
@@ -23638,7 +23652,7 @@
-
+
@@ -23813,11 +23827,12 @@
+
+
-
@@ -23870,7 +23885,7 @@
-
+
@@ -26732,11 +26747,12 @@
+
+
-
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/model/bad/InvalidMixinDeclarations.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/model/bad/InvalidMixinDeclarations.java
new file mode 100644
index 00000000000..1ecf65dbec5
--- /dev/null
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/model/bad/InvalidMixinDeclarations.java
@@ -0,0 +1,80 @@
+/*
+ * 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.testdomain.model.bad;
+
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.ActionLayout;
+import org.apache.causeway.applib.annotation.Collection;
+import org.apache.causeway.applib.annotation.CollectionLayout;
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.Introspection;
+import org.apache.causeway.applib.annotation.MemberSupport;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.annotation.PropertyLayout;
+
+import lombok.RequiredArgsConstructor;
+
+@DomainObject(nature = Nature.VIEW_MODEL, introspection = Introspection.ANNOTATION_REQUIRED)
+@Named("testdomain.InvalidMixinDeclarations")
+public class InvalidMixinDeclarations {
+
+ // -- INVALID SCENARIOS
+
+ @Action
+ @ActionLayout(named = "someActionProp")
+ @RequiredArgsConstructor
+ public static class ActionMixinWithProp {
+ @SuppressWarnings("unused")
+ private final InvalidMixinDeclarations mixee;
+ @MemberSupport public String other() { return ""; }
+ }
+
+ @Action
+ @ActionLayout(named = "someActionColl")
+ @RequiredArgsConstructor
+ public static class ActionMixinWithColl {
+ @SuppressWarnings("unused")
+ private final InvalidMixinDeclarations mixee;
+ @MemberSupport public List other() { return List.of(); }
+ }
+
+ @Property
+ @PropertyLayout(named = "someProperty")
+ @RequiredArgsConstructor
+ public static class PropertyMixinWithOther {
+ @SuppressWarnings("unused")
+ private final InvalidMixinDeclarations mixee;
+ @MemberSupport public void other() { }
+ }
+
+ @Collection
+ @CollectionLayout(named = "someCollection")
+ @RequiredArgsConstructor
+ public static class CollectionMixinWithOther {
+ @SuppressWarnings("unused")
+ private final InvalidMixinDeclarations mixee;
+ @MemberSupport public void other() { }
+ }
+
+}