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 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. + *

+ */ +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() { } + } + +}