Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import org.springframework.context.annotation.Profile;

import org.apache.causeway.applib.annotation.Domain;
import org.apache.causeway.applib.annotation.Programmatic;

/**
Expand Down Expand Up @@ -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 <i>entity</i>, <i>viewmodel</i> or <i>domain-service</i>
* to act as contributer of a single <i>domain-action</i> or
* <i>domain-property</i> or <i>domain-collection</i>.
*/
MIXIN,
/**
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <i>Action</i>, <i>Property</i> or <i>Collection</i>.
* <p>
* Since v2 only ever used for mixed in actions.
* @since 2.0
*/
public interface ContributingFacet extends Facet {

enum Contributing {

/**
* Contributed as an action but <i>not</i> 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 <i>not</i>
* 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> visitor) {
super.visitAttributes(visitor);
visitor.accept("contributing", contributing);
visitor.accept("contributing", contributed());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -68,22 +73,11 @@ Optional<Collection> collectionIfAny(final ProcessMethodContext processMethodCon
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), Collection.class));
}

void inferIntentWhenOnTypeLevel(final ProcessMethodContext processMethodContext, final Optional<Collection> 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<Collection> collectionIfAny) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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<String> {
public interface MixinFacet extends Facet {

public enum Contributing {
/**
* Initial state early during introspection.
*/
UNSPECIFIED,
/**
* Object associated with an <i>entity</i>, <i>viewmodel</i> or <i>domain-service</i>
* to act as contributer of a single <i>domain-action</i>.
*/
AS_ACTION,
/**
* Object associated with an <i>entity</i>, <i>viewmodel</i> or <i>domain-service</i>
* to act as contributer of a single <i>domain-property</i>.
*/
AS_PROPERTY,
/**
* Object associated with an <i>entity</i>, <i>viewmodel</i> or <i>domain-service</i>
* to act as contributer of a single <i>domain-collection</i>.
*/
AS_COLLECTION;

public boolean isUnspecified() { return this==UNSPECIFIED; }
}

Contributing contributing();

/**
* The mixin's main method name.
*/
String getMainMethodName();

boolean isMixinFor(Class<?> candidateDomainType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>
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;
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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());
Expand All @@ -113,15 +120,22 @@ public boolean isCandidateForMain(final Method method) {
public void visitAttributes(final BiConsumer<String, Object> 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ private MixinFacetForDomainObjectAnnotation(
final FacetHolder holder) {
super(mixinClass, mixinMethodName, constructor, holder);
}

}
Loading