This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch v3
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/v3 by this push:
new b832e0a6b31 CAUSEWAY-3982: extend auditing to only log on updates, not
create or delete (cherrypicked from v4)
b832e0a6b31 is described below
commit b832e0a6b3188055a2598705f7fcc44f863793dd
Author: andi-huber <[email protected]>
AuthorDate: Fri Mar 27 06:36:07 2026 +0100
CAUSEWAY-3982: extend auditing to only log on updates, not create or
delete (cherrypicked from v4)
---
.../causeway/applib/annotation/Publishing.java | 21 +-
.../CommandPublishingFacetForActionAnnotation.java | 55 +-
...ommandPublishingFacetForPropertyAnnotation.java | 80 +-
...xecutionPublishingFacetForActionAnnotation.java | 46 +-
...cutionPublishingFacetForPropertyAnnotation.java | 82 +-
...gePublishingFacetForDomainObjectAnnotation.java | 35 +-
...FacetForDomainObjectAnnotationAsConfigured.java | 2 +-
...tityChangePublishingFacetFromConfiguration.java | 2 +-
.../entitychange/EntityChangePublishingFacet.java | 45 +-
.../EntityChangePublishingFacetAbstract.java | 12 +-
.../EntityPropertyChangePublishingPolicyFacet.java | 11 +-
...PublishingPolicyFacetForPropertyAnnotation.java | 6 +-
.../metamodel/facets/FacetFactoryTestAbstract.java | 484 +++++++++++
.../DomainObjectAnnotationFacetFactoryTest.java | 768 ++++++++++++++++++
.../PropertyAnnotationFacetFactoryTest.java | 885 +++++++++++++++++++++
.../changetracking/EntityChangeTrackerDefault.java | 44 +-
16 files changed, 2356 insertions(+), 222 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/annotation/Publishing.java
b/api/applib/src/main/java/org/apache/causeway/applib/annotation/Publishing.java
index b0fcc085537..c6125513cdb 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/annotation/Publishing.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/annotation/Publishing.java
@@ -45,8 +45,8 @@ public enum Publishing {
* Publishing of data triggered by interaction with this object
* should be handled as per the default publishing policy
* configured in <tt>application.properties</tt>.
- * <p>
- * If no publishing policy is configured, then publishing is disabled.
+ *
+ * <p>If no publishing policy is configured, then publishing is disabled.
*/
AS_CONFIGURED,
@@ -55,6 +55,23 @@ public enum Publishing {
*/
ENABLED,
+ /**
+ * Applies only to {@link EntityPropertyChangeSubscriber}, whereby events
are published for modifications to the
+ * object, but no events are published for the initial creation of an
object.
+ *
+ * <p>In the case of audit trail extension,
+ * this effectively suppresses all of the "[NEW] -> value" entries that
are created for every property of the
+ * entity when it is being created, and also all of the "value ->
[DELETED]" entries that are created for every property of the
+ * entity when it is being deleted.
+ *
+ * <p>This variant is intended only where the application code has enough
traceability built into the domain
+ * (perhaps to provide visibility to the end-users) that the technical
auditing is overkill. It will also
+ * of course reduce the volume of auditing, so improves performance
(likely both response times and throughput).
+ *
+ * <p>For other subscribers, behaviour is the same as {@link #ENABLED}.
+ */
+ ENABLED_FOR_UPDATES_ONLY,
+
/**
* Do <b>not</b> publish data triggered by interaction with this object
* (even if otherwise configured to enable publishing).
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForActionAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForActionAnnotation.java
index c4ec4abb732..b7f35a5f8ae 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForActionAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForActionAnnotation.java
@@ -62,40 +62,33 @@ public static Optional<CommandPublishingFacet> create(
var publishingPolicy =
ActionConfigOptions.actionCommandPublishingPolicy(configuration);
return actionsIfAny
- .filter(action -> action.commandPublishing() !=
Publishing.NOT_SPECIFIED)
- .map(action -> {
- Publishing publishing = action.commandPublishing();
+ .filter(action -> action.commandPublishing() !=
Publishing.NOT_SPECIFIED)
+ .map(action -> {
+ Publishing publishing = action.commandPublishing();
- final Class<? extends CommandDtoProcessor> processorClass
= action.commandDtoProcessor();
- final CommandDtoProcessor processor =
newProcessorElseNull(processorClass);
+ final Class<? extends CommandDtoProcessor> processorClass =
action.commandDtoProcessor();
+ final CommandDtoProcessor processor =
newProcessorElseNull(processorClass);
- if(processor != null) {
- publishing = Publishing.ENABLED;
- }
+ if(processor != null) {
+ publishing = Publishing.ENABLED;
+ }
- switch (publishing) {
- case AS_CONFIGURED:
- switch (publishingPolicy) {
- case NONE:
- return new
CommandPublishingFacetForActionAnnotationAsConfigured.None(holder,
servicesInjector);
- case IGNORE_QUERY_ONLY:
- case IGNORE_SAFE:
- return Facets.hasSafeSemantics(holder)
- ? new
CommandPublishingFacetForActionAnnotationAsConfigured.IgnoreSafe(holder,
servicesInjector)
- : new
CommandPublishingFacetForActionAnnotationAsConfigured.IgnoreSafeYetNot(holder,
servicesInjector);
- case ALL:
- return new
CommandPublishingFacetForActionAnnotationAsConfigured.All(holder,
servicesInjector);
- default:
- throw new
IllegalStateException(String.format("configured action.commandPublishing policy
'%s' not recognised", publishingPolicy));
- }
- case DISABLED:
- return new
CommandPublishingFacetForActionAnnotation.Disabled(processor, holder,
servicesInjector);
- case ENABLED:
- return new
CommandPublishingFacetForActionAnnotation.Enabled(processor, holder,
servicesInjector);
- default:
- throw new
IllegalStateException(String.format("@Action#commandPublishing '%s' not
recognised", publishing));
- }
- });
+ return switch (publishing) {
+ case AS_CONFIGURED -> switch (publishingPolicy) {
+ case NONE -> new
CommandPublishingFacetForActionAnnotationAsConfigured.None(holder,
servicesInjector);
+ case IGNORE_QUERY_ONLY, IGNORE_SAFE ->
Facets.hasSafeSemantics(holder)
+ ? new
CommandPublishingFacetForActionAnnotationAsConfigured.IgnoreSafe(holder,
servicesInjector)
+ : new
CommandPublishingFacetForActionAnnotationAsConfigured.IgnoreSafeYetNot(holder,
servicesInjector);
+ case ALL -> new
CommandPublishingFacetForActionAnnotationAsConfigured.All(holder,
servicesInjector);
+ default -> throw new IllegalStateException(
+ String.format("configured
action.commandPublishing policy '%s' not recognised", publishingPolicy));
+ };
+ case DISABLED -> new
CommandPublishingFacetForActionAnnotation.Disabled(processor, holder,
servicesInjector);
+ case ENABLED, ENABLED_FOR_UPDATES_ONLY -> new
CommandPublishingFacetForActionAnnotation.Enabled(processor, holder,
servicesInjector);
+ default -> throw new IllegalStateException(
+ String.format("@Action#commandPublishing '%s' not
recognised", publishing));
+ };
+ });
}
CommandPublishingFacetForActionAnnotation(
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForPropertyAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForPropertyAnnotation.java
index 873470d9746..642a9edd462 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForPropertyAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/command/CommandPublishingFacetForPropertyAnnotation.java
@@ -68,7 +68,7 @@ public static CommandPublishingFacet create(
return propertyIfAny
.filter(property -> property.commandPublishing() !=
Publishing.NOT_SPECIFIED)
- .map(property -> {
+ .<CommandPublishingFacet>map(property -> {
Publishing publishing = property.commandPublishing();
var processorClass = property.commandDtoProcessor();
@@ -78,64 +78,54 @@ public static CommandPublishingFacet create(
publishing = Publishing.ENABLED;
}
- switch (publishing) {
- case AS_CONFIGURED:
- switch (publishingPolicy) {
- case NONE:
- return (CommandPublishingFacet)new
CommandPublishingFacetForPropertyAnnotationAsConfigured.None(holder,
servicesInjector);
- case ALL:
- return new
CommandPublishingFacetForPropertyAnnotationAsConfigured.All(holder,
servicesInjector);
- default:
- throw new
IllegalStateException(String.format("configured property.commandpublishing
policy '%s' not recognised", publishingPolicy));
- }
- case DISABLED:
- return new
CommandPublishingFacetForPropertyAnnotation.Disabled(processor, holder,
servicesInjector);
- case ENABLED:
- return new
CommandPublishingFacetForPropertyAnnotation.Enabled(processor, holder,
servicesInjector);
- default:
- throw new
IllegalStateException(String.format("@Property#commandPublishing '%s' not
recognised", publishing));
- }
+ return switch (publishing) {
+ case AS_CONFIGURED -> switch (publishingPolicy) {
+ case NONE -> new
CommandPublishingFacetForPropertyAnnotationAsConfigured.None(holder,
servicesInjector);
+ case ALL -> new
CommandPublishingFacetForPropertyAnnotationAsConfigured.All(holder,
servicesInjector);
+ default -> throw new IllegalStateException(
+ String.format("configured
property.commandpublishing policy '%s' not recognised", publishingPolicy));
+ };
+ case DISABLED -> new
CommandPublishingFacetForPropertyAnnotation.Disabled(processor, holder,
servicesInjector);
+ case ENABLED, ENABLED_FOR_UPDATES_ONLY -> new
CommandPublishingFacetForPropertyAnnotation.Enabled(processor, holder,
servicesInjector);
+ default -> throw new IllegalStateException(
+ String.format("@Property#commandPublishing '%s'
not recognised", publishing));
+ };
})
.orElseGet(() -> {
// there is no publishing facet from either @Action or
@Property, so use the appropriate configuration to install a default
- if (representsProperty(holder)) {
+ if (representsProperty(holder))
// we are dealing with a property
- switch (publishingPolicy) {
- case NONE:
- return new
CommandPublishingFacetForPropertyFromConfiguration.None(holder,
servicesInjector);
- case ALL:
- return new
CommandPublishingFacetForPropertyFromConfiguration.All(holder,
servicesInjector);
- default:
- throw new
IllegalStateException(String.format("configured property.commandPublishing
policy '%s' not recognised", publishingPolicy));
- }
- } else {
+ return switch (publishingPolicy) {
+ case NONE -> new
CommandPublishingFacetForPropertyFromConfiguration.None(holder,
servicesInjector);
+ case ALL -> new
CommandPublishingFacetForPropertyFromConfiguration.All(holder,
servicesInjector);
+ default -> throw new
IllegalStateException(String.format("configured property.commandPublishing
policy '%s' not recognised", publishingPolicy));
+ };
+ else {
// we are dealing with an action
var actionPublishingPolicy =
ActionConfigOptions.actionCommandPublishingPolicy(configuration);
- switch (actionPublishingPolicy) {
- case NONE:
- return new
CommandPublishingFacetForActionFromConfiguration.None(holder, servicesInjector);
- case IGNORE_QUERY_ONLY:
- case IGNORE_SAFE:
- return Facets.hasSafeSemantics(holder)
- ? new
CommandPublishingFacetForActionFromConfiguration.IgnoreSafe(holder,
servicesInjector)
- : new
CommandPublishingFacetForActionFromConfiguration.IgnoreSafeYetNot(holder,
servicesInjector);
- case ALL:
- return new
CommandPublishingFacetForActionFromConfiguration.All(holder, servicesInjector);
- default:
- throw new
IllegalStateException(String.format("configured action.commandPublishing policy
'%s' not recognised", actionPublishingPolicy));
- }
+ return switch (actionPublishingPolicy) {
+ case NONE -> new
CommandPublishingFacetForActionFromConfiguration.None(holder, servicesInjector);
+ case IGNORE_QUERY_ONLY, IGNORE_SAFE ->
Facets.hasSafeSemantics(holder)
+ ? new
CommandPublishingFacetForActionFromConfiguration.IgnoreSafe(holder,
servicesInjector)
+ : new
CommandPublishingFacetForActionFromConfiguration.IgnoreSafeYetNot(holder,
servicesInjector);
+ case ALL -> new
CommandPublishingFacetForActionFromConfiguration.All(holder, servicesInjector);
+ default -> throw new
IllegalStateException(String.format("configured action.commandPublishing policy
'%s' not recognised", actionPublishingPolicy));
+ };
}
});
+
}
private static boolean representsProperty(final FacetHolder holder) {
// a property
- if (holder instanceof TypedFacetHolder &&
((TypedFacetHolder)holder).featureType() == FeatureType.PROPERTY) {
+ if (holder instanceof TypedFacetHolder typedFacetHolder
+ && typedFacetHolder.featureType() == FeatureType.PROPERTY)
return true;
- }
// or a mixin
- return holder.containsFacet(ContributingFacet.class) &&
- holder.getFacet(ContributingFacet.class).contributed() ==
MixinFacet.Contributing.AS_PROPERTY;
+ return holder.lookupFacet(ContributingFacet.class)
+ .map(ContributingFacet::contributed)
+ .map(MixinFacet.Contributing.AS_PROPERTY::equals)
+ .orElse(false);
}
CommandPublishingFacetForPropertyAnnotation(
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForActionAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForActionAnnotation.java
index 14b780a8d5f..67b52d6bdb8 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForActionAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForActionAnnotation.java
@@ -58,34 +58,24 @@ public static Optional<ExecutionPublishingFacet> create(
var publishingPolicy =
ActionConfigOptions.actionExecutionPublishingPolicy(configuration);
- return
- actionsIfAny
- .filter(action -> action.executionPublishing() !=
Publishing.NOT_SPECIFIED)
- .map(Action::executionPublishing)
- .<ExecutionPublishingFacet>map(publishing -> {
- switch (publishing) {
- case AS_CONFIGURED:
- switch (publishingPolicy) {
- case NONE:
- return new
ExecutionPublishingFacetForActionAnnotationAsConfigured.None(holder);
- case IGNORE_QUERY_ONLY:
- case IGNORE_SAFE:
- return Facets.hasSafeSemantics(holder)
- ? new
ExecutionPublishingFacetForActionAnnotationAsConfigured.IgnoreSafe(holder)
- : new
ExecutionPublishingFacetForActionAnnotationAsConfigured.IgnoreSafeYetNot(holder);
- case ALL:
- return new
ExecutionPublishingFacetForActionAnnotationAsConfigured.All(holder);
- default:
- throw new
IllegalStateException(String.format("configured action.executionPublishing
policy '%s' not recognised", publishingPolicy));
- }
- case DISABLED:
- return new
ExecutionPublishingFacetForActionAnnotation.Disabled(holder);
- case ENABLED:
- return new
ExecutionPublishingFacetForActionAnnotation.Enabled(holder);
- default:
- throw new
IllegalStateException(String.format("@Action#executionPublishing '%s' not
recognised", publishing));
- }
- });
+ return actionsIfAny
+ .filter(action -> action.executionPublishing() !=
Publishing.NOT_SPECIFIED)
+ .map(Action::executionPublishing)
+ .<ExecutionPublishingFacet>map(publishing -> (switch (publishing) {
+ case AS_CONFIGURED -> switch (publishingPolicy) {
+ case NONE -> new
ExecutionPublishingFacetForActionAnnotationAsConfigured.None(holder);
+ case IGNORE_QUERY_ONLY, IGNORE_SAFE ->
Facets.hasSafeSemantics(holder)
+ ? new
ExecutionPublishingFacetForActionAnnotationAsConfigured.IgnoreSafe(holder)
+ : new
ExecutionPublishingFacetForActionAnnotationAsConfigured.IgnoreSafeYetNot(holder);
+ case ALL -> new
ExecutionPublishingFacetForActionAnnotationAsConfigured.All(holder);
+ default -> throw new IllegalStateException(
+ String.format("configured
action.executionPublishing policy '%s' not recognised", publishingPolicy));
+ };
+ case DISABLED -> new
ExecutionPublishingFacetForActionAnnotation.Disabled(holder);
+ case ENABLED, ENABLED_FOR_UPDATES_ONLY -> new
ExecutionPublishingFacetForActionAnnotation.Enabled(holder);
+ default -> throw new IllegalStateException(
+ String.format("@Action#executionPublishing '%s' not
recognised", publishing));
+ }));
}
ExecutionPublishingFacetForActionAnnotation(final FacetHolder holder) {
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForPropertyAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForPropertyAnnotation.java
index ec9fcc705df..f08b54d6e34 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForPropertyAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/members/publish/execution/ExecutionPublishingFacetForPropertyAnnotation.java
@@ -67,66 +67,54 @@ public static ExecutionPublishingFacet create(
return propertyIfAny
.map(Property::executionPublishing)
.filter(publishing -> publishing != Publishing.NOT_SPECIFIED)
- .map(publishing -> {
-
- switch (publishing) {
- case AS_CONFIGURED:
- switch (publishingPolicy) {
- case NONE:
- return (ExecutionPublishingFacet)new
ExecutionPublishingFacetForPropertyAnnotationAsConfigured.None(holder);
- case ALL:
- return new
ExecutionPublishingFacetForPropertyAnnotationAsConfigured.All(holder);
- default:
- throw new
IllegalStateException(String.format("configured property.executionPublishing
policy '%s' not recognised", publishingPolicy));
- }
- case DISABLED:
- return new
ExecutionPublishingFacetForPropertyAnnotation.Disabled(holder);
- case ENABLED:
- return new
ExecutionPublishingFacetForPropertyAnnotation.Enabled(holder);
- default:
- throw new
IllegalStateException(String.format("@Property#executionPublishing '%s' not
recognised", publishing));
- }
- })
+ .<ExecutionPublishingFacet>map(publishing -> (switch (publishing) {
+ case AS_CONFIGURED -> switch (publishingPolicy) {
+ case NONE -> new
ExecutionPublishingFacetForPropertyAnnotationAsConfigured.None(holder);
+ case ALL -> new
ExecutionPublishingFacetForPropertyAnnotationAsConfigured.All(holder);
+ default -> throw new IllegalStateException(
+ String.format("configured property.executionPublishing
policy '%s' not recognised", publishingPolicy));
+ };
+ case DISABLED -> new
ExecutionPublishingFacetForPropertyAnnotation.Disabled(holder);
+ case ENABLED, ENABLED_FOR_UPDATES_ONLY -> new
ExecutionPublishingFacetForPropertyAnnotation.Enabled(holder);
+ default -> throw new IllegalStateException(
+ String.format("@Property#executionPublishing '%s' not
recognised", publishing));
+ }))
.orElseGet(() -> {
// there is no publishing facet from either @Action or
@Property, so use the appropriate configuration to install a default
- if (representsProperty(holder)) {
+ if (representsProperty(holder))
// we are dealing with a property
- switch (publishingPolicy) {
- case NONE:
- return new
ExecutionPublishingFacetForPropertyFromConfiguration.None(holder);
- case ALL:
- return new
ExecutionPublishingFacetForPropertyFromConfiguration.All(holder);
- default:
- throw new
IllegalStateException(String.format("configured property.executionPublishing
policy '%s' not recognised", publishingPolicy));
- }
- } else {
+ return switch (publishingPolicy) {
+ case NONE -> new
ExecutionPublishingFacetForPropertyFromConfiguration.None(holder);
+ case ALL -> new
ExecutionPublishingFacetForPropertyFromConfiguration.All(holder);
+ default -> throw new IllegalStateException(
+ String.format("configured
property.executionPublishing policy '%s' not recognised", publishingPolicy));
+ };
+ else {
// we are dealing with an action
var actionPublishingPolicy =
ActionConfigOptions.actionExecutionPublishingPolicy(configuration);
- switch (actionPublishingPolicy) {
- case NONE:
- return new
ExecutionPublishingFacetForActionFromConfiguration.None(holder);
- case IGNORE_QUERY_ONLY:
- case IGNORE_SAFE:
- return Facets.hasSafeSemantics(holder)
- ? new
ExecutionPublishingFacetForActionFromConfiguration.IgnoreSafe(holder)
- : new
ExecutionPublishingFacetForActionFromConfiguration.IgnoreSafeYetNot(holder);
- case ALL:
- return new
ExecutionPublishingFacetForActionFromConfiguration.All(holder);
- default:
- throw new
IllegalStateException(String.format("configured action.executionPublishing
policy '%s' not recognised", actionPublishingPolicy));
- }
+ return switch (actionPublishingPolicy) {
+ case NONE -> new
ExecutionPublishingFacetForActionFromConfiguration.None(holder);
+ case IGNORE_QUERY_ONLY, IGNORE_SAFE ->
Facets.hasSafeSemantics(holder)
+ ? new
ExecutionPublishingFacetForActionFromConfiguration.IgnoreSafe(holder)
+ : new
ExecutionPublishingFacetForActionFromConfiguration.IgnoreSafeYetNot(holder);
+ case ALL -> new
ExecutionPublishingFacetForActionFromConfiguration.All(holder);
+ default -> throw new IllegalStateException(
+ String.format("configured
action.executionPublishing policy '%s' not recognised",
actionPublishingPolicy));
+ };
}
});
}
private static boolean representsProperty(final FacetHolder holder) {
// a property
- if (holder instanceof TypedFacetHolder &&
((TypedFacetHolder)holder).featureType() == FeatureType.PROPERTY) {
+ if (holder instanceof TypedFacetHolder typedFacetHolder
+ && typedFacetHolder.featureType() == FeatureType.PROPERTY)
return true;
- }
// or a mixin
- return holder.containsFacet(ContributingFacet.class) &&
- holder.getFacet(ContributingFacet.class).contributed() ==
MixinFacet.Contributing.AS_PROPERTY;
+ return holder.lookupFacet(ContributingFacet.class)
+ .map(ContributingFacet::contributed)
+ .map(MixinFacet.Contributing.AS_PROPERTY::equals)
+ .orElse(false);
}
public ExecutionPublishingFacetForPropertyAnnotation(final FacetHolder
holder) {
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotation.java
index 9956501d41c..56a686b2250 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotation.java
@@ -38,32 +38,27 @@ public static Optional<EntityChangePublishingFacet> create(
var publish =
entityChangePublishingIfAny.orElse(Publishing.AS_CONFIGURED);
- switch (publish) {
- case NOT_SPECIFIED:
- case AS_CONFIGURED:
-
- var publishingPolicy =
DomainObjectConfigOptions.entityChangePublishingPolicy(configuration);
- switch (publishingPolicy) {
- case NONE:
- return Optional.of(entityChangePublishingIfAny.isPresent()
+ return switch (publish) {
+ case NOT_SPECIFIED, AS_CONFIGURED -> {
+ var publishingPolicy =
DomainObjectConfigOptions.entityChangePublishingPolicy(configuration);
+ yield switch (publishingPolicy) {
+ case NONE ->
Optional.of(entityChangePublishingIfAny.isPresent()
? new
EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured(holder, false)
: new
EntityChangePublishingFacetFromConfiguration(holder, false));
- default:
- return Optional.of(entityChangePublishingIfAny.isPresent()
+ default ->
Optional.of(entityChangePublishingIfAny.isPresent()
? new
EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured(holder, true)
: new
EntityChangePublishingFacetFromConfiguration(holder, true));
+ };
}
- case DISABLED:
- return Optional.of(new
EntityChangePublishingFacetForDomainObjectAnnotation(holder, false));
- case ENABLED:
- return Optional.of(new
EntityChangePublishingFacetForDomainObjectAnnotation(holder, true));
-
- default:
- throw _Exceptions.unmatchedCase(publish);
- }
+ case DISABLED -> Optional.of(new
EntityChangePublishingFacetForDomainObjectAnnotation(holder, false, false,
false, false));
+ case ENABLED -> Optional.of(new
EntityChangePublishingFacetForDomainObjectAnnotation(holder, true, true, true,
true));
+ case ENABLED_FOR_UPDATES_ONLY -> Optional.of(new
EntityChangePublishingFacetForDomainObjectAnnotation(holder, true, false, true,
false));
+ default -> throw _Exceptions.unmatchedCase(publish);
+ };
}
- protected EntityChangePublishingFacetForDomainObjectAnnotation(final
FacetHolder holder, boolean enabled) {
- super(holder, enabled);
+ protected EntityChangePublishingFacetForDomainObjectAnnotation(final
FacetHolder holder, final boolean enabled,
+ final boolean enabledForCreate, final boolean enabledForUpdate,
final boolean enabledForDelete) {
+ super(holder, enabled, enabledForCreate, enabledForUpdate,
enabledForDelete);
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured.java
index df5890d9df7..e724d79135d 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured.java
@@ -23,7 +23,7 @@
public class EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured
extends EntityChangePublishingFacetForDomainObjectAnnotation {
public
EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured(final
FacetHolder facetHolder, final boolean enabled) {
- super(facetHolder, enabled);
+ super(facetHolder, enabled, enabled, enabled, enabled);
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetFromConfiguration.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetFromConfiguration.java
index cdeebda521c..0ef4aef74cf 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetFromConfiguration.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/entitychangepublishing/EntityChangePublishingFacetFromConfiguration.java
@@ -30,7 +30,7 @@ public class EntityChangePublishingFacetFromConfiguration
extends EntityChangePublishingFacetAbstract {
public EntityChangePublishingFacetFromConfiguration(final FacetHolder
facetHolder, final boolean enabled) {
- super(facetHolder, enabled);
+ super(facetHolder, enabled, enabled, enabled, enabled);
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacet.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacet.java
index 91513f3bee0..c8b869cdc12 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacet.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacet.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.core.metamodel.facets.object.publish.entitychange;
+import java.util.Optional;
+
import org.apache.causeway.core.metamodel.facetapi.Facet;
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -30,22 +32,41 @@
*/
public interface EntityChangePublishingFacet extends Facet {
- public static boolean isPublishingEnabled(final FacetHolder facetHolder) {
- if(facetHolder==null) {
- return false;
- }
+ static boolean isPublishingEnabled(final FacetHolder facetHolder) {
+ return entityChangePublishingFacet(facetHolder)
+ .map(EntityChangePublishingFacet::isEnabled)
+ .orElse(false);
+ }
+
+ static boolean isPublishingEnabledForCreate(final FacetHolder facetHolder)
{
+ return entityChangePublishingFacet(facetHolder)
+ .map(EntityChangePublishingFacet::isEnabledForCreate)
+ .orElse(false);
+ }
- if(facetHolder instanceof ObjectSpecification) {
- if(!((ObjectSpecification)facetHolder).isEntity()) {
- return false;
- }
- }
+ static boolean isPublishingEnabledForUpdate(final FacetHolder facetHolder)
{
+ return entityChangePublishingFacet(facetHolder)
+ .map(EntityChangePublishingFacet::isEnabledForUpdate)
+ .orElse(false);
+ }
- var entityChangePublishingFacet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
- return entityChangePublishingFacet != null
- && entityChangePublishingFacet.isEnabled();
+ static boolean isPublishingEnabledForDelete(final FacetHolder facetHolder)
{
+ return entityChangePublishingFacet(facetHolder)
+ .map(EntityChangePublishingFacet::isEnabledForDelete)
+ .orElse(false);
}
boolean isEnabled();
+ boolean isEnabledForCreate();
+ boolean isEnabledForUpdate();
+ boolean isEnabledForDelete();
+ private static Optional<EntityChangePublishingFacet>
entityChangePublishingFacet(final FacetHolder facetHolder) {
+ if(facetHolder==null)
+ return Optional.empty();
+ if(facetHolder instanceof ObjectSpecification objSpepc
+ && !objSpepc.isEntity())
+ return Optional.empty(); // optimization
+ return facetHolder.lookupFacet(EntityChangePublishingFacet.class);
+ }
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacetAbstract.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacetAbstract.java
index af60b3f0544..20ce78bf62f 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacetAbstract.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/publish/entitychange/EntityChangePublishingFacetAbstract.java
@@ -32,12 +32,18 @@ private static final Class<? extends Facet> type() {
return EntityChangePublishingFacet.class;
}
- @Getter
- private final boolean enabled;
+ @Getter private final boolean enabled;
+ @Getter private final boolean enabledForCreate;
+ @Getter private final boolean enabledForUpdate;
+ @Getter private final boolean enabledForDelete;
- public EntityChangePublishingFacetAbstract(final FacetHolder facetHolder,
boolean enabled) {
+ public EntityChangePublishingFacetAbstract(final FacetHolder facetHolder,
final boolean enabled,
+ final boolean enabledForCreate, final boolean enabledForUpdate,
final boolean enabledForDelete) {
super(EntityChangePublishingFacetAbstract.type(), facetHolder);
this.enabled = enabled;
+ this.enabledForCreate = enabledForCreate;
+ this.enabledForUpdate = enabledForUpdate;
+ this.enabledForDelete = enabledForDelete;
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacet.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacet.java
index 6eca0d5a86a..4ea8c167ac0 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacet.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacet.java
@@ -18,14 +18,14 @@
*/
package
org.apache.causeway.core.metamodel.facets.properties.property.entitychangepublishing;
+import org.jspecify.annotations.NonNull;
+
import org.apache.causeway.applib.annotation.Publishing;
import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.Clob;
import org.apache.causeway.core.metamodel.facetapi.Facet;
import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
-import org.jspecify.annotations.NonNull;
-
/**
* Indicates whether a property should be excluded from entity change
publishing (auditing).
* @since 2.0
@@ -33,7 +33,7 @@
public interface EntityPropertyChangePublishingPolicyFacet extends Facet {
/**
- * Must be one of Publishing.ENABLED or Publishing.DISABLED.
+ * Must be one of {@link Publishing#ENABLED}, {@link
Publishing#ENABLED_FOR_UPDATES_ONLY} or {@link Publishing#DISABLED}.
*/
@NonNull Publishing getEntityChangePublishing();
@@ -42,7 +42,8 @@ default boolean isPublishingVetoed() {
}
default boolean isPublishingAllowed() {
- return getEntityChangePublishing() == Publishing.ENABLED;
+ return getEntityChangePublishing() == Publishing.ENABLED
+ || getEntityChangePublishing() ==
Publishing.ENABLED_FOR_UPDATES_ONLY;
}
static boolean isExcludedFromPublishing(final @NonNull OneToOneAssociation
property) {
@@ -58,7 +59,7 @@ static boolean isExcludedFromPublishing(final @NonNull
OneToOneAssociation prope
.map(EntityPropertyChangePublishingPolicyFacet::isPublishingAllowed)
.orElse(false);
- //XXX CAUSEWAY-1488, exclude Bob/Clob from property change
publishing unless explicitly allowed
+ //XXX CAUSEWAY-1488, exclude Blob/Clob from property change
publishing unless explicitly allowed
return !isExplictlyAllowed;
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacetForPropertyAnnotation.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacetForPropertyAnnotation.java
index dbf4cc19abc..bcba7479eb2 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacetForPropertyAnnotation.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/entitychangepublishing/EntityPropertyChangePublishingPolicyFacetForPropertyAnnotation.java
@@ -34,9 +34,9 @@ public static
Optional<EntityPropertyChangePublishingPolicyFacet> create(
return propertyIfAny
.map(Property::entityChangePublishing)
// only install facet if policy is explicit ('enabled' or
'disabled')
- .filter(entityChangePublishing ->
- entityChangePublishing == Publishing.ENABLED
- || entityChangePublishing == Publishing.DISABLED)
+ .filter(entityChangePublishing -> entityChangePublishing ==
Publishing.ENABLED
+ || entityChangePublishing ==
Publishing.ENABLED_FOR_UPDATES_ONLY
+ || entityChangePublishing == Publishing.DISABLED)
.map(entityChangePublishing ->
new
EntityPropertyChangePublishingPolicyFacetForPropertyAnnotation(entityChangePublishing,
holder));
}
diff --git
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java
new file mode 100644
index 00000000000..a286ff984aa
--- /dev/null
+++
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/FacetFactoryTestAbstract.java
@@ -0,0 +1,484 @@
+/*
+ * 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.facets;
+
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import org.jspecify.annotations.NonNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
+import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.applib.services.i18n.TranslationService;
+import org.apache.causeway.applib.services.iactnlayer.InteractionService;
+import org.apache.causeway.commons.internal.assertions._Assert;
+import
org.apache.causeway.commons.internal.reflection._GenericResolver.ResolvedMethod;
+import org.apache.causeway.commons.internal.reflection._MethodFacades;
+import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
+import org.apache.causeway.core.metamodel.context.MetaModelContext;
+import org.apache.causeway.core.metamodel.execution.MemberExecutorService;
+import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.facetapi.FeatureType;
+import
org.apache.causeway.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import
org.apache.causeway.core.metamodel.facets.FacetFactory.ProcessMethodContext;
+import
org.apache.causeway.core.metamodel.facets.FacetFactory.ProcessParameterContext;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.core.metamodel.spec.impl._JUnitSupport;
+import org.apache.causeway.core.metamodel.valuesemantics.IntValueSemantics;
+import org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting;
+import org.apache.causeway.core.mmtestsupport.MethodRemover_forTesting;
+import
org.apache.causeway.core.security.authentication.InteractionContextFactory;
+
+import lombok.Builder;
+import lombok.Getter;
+
+public abstract class FacetFactoryTestAbstract
+implements HasMetaModelContext {
+
+ // -- SCENARIO BUILDER
+
+ @Builder
+ public record ActionScenario(
+ Class<?> declaringClass,
+ String actionName,
+ Optional<Class<?>> mixinClass) {
+ public static ActionScenarioBuilder builder(final Class<?>
declaringClass, final String actionName) {
+ return new ActionScenario.ActionScenarioBuilder()
+ .mixinClass(Optional.empty())
+ .declaringClass(declaringClass)
+ .actionName(actionName);
+ }
+ }
+
+ @Builder
+ public record ParameterScenario(
+ Class<?> declaringClass,
+ String actionName,
+ int paramIndex,
+ Optional<Class<?>> mixinClass) {
+ public static ParameterScenarioBuilder builder(final Class<?>
declaringClass, final String actionName, final int paramIndex) {
+ return new ParameterScenario.ParameterScenarioBuilder()
+ .mixinClass(Optional.empty())
+ .declaringClass(declaringClass)
+ .actionName(actionName)
+ .paramIndex(paramIndex);
+ }
+ }
+
+ @Builder
+ public record PropertyScenario(
+ Class<?> declaringClass,
+ String propertyName,
+ Optional<Class<?>> mixinClass) {
+ public static PropertyScenarioBuilder builder(final Class<?>
declaringClass, final String propertyName) {
+ return new PropertyScenario.PropertyScenarioBuilder()
+ .mixinClass(Optional.empty())
+ .declaringClass(declaringClass)
+ .propertyName(propertyName);
+ }
+ }
+
+ @Builder
+ public record CollectionScenario(
+ Class<?> declaringClass,
+ String collectionName,
+ Optional<Class<?>> mixinClass) {
+ public static CollectionScenarioBuilder builder(final Class<?>
declaringClass, final String collectionName) {
+ return new CollectionScenario.CollectionScenarioBuilder()
+ .mixinClass(Optional.empty())
+ .declaringClass(declaringClass)
+ .collectionName(collectionName);
+ }
+ }
+
+ // -- SETUP
+
+ @Getter(onMethod_ = {@Override}) protected MetaModelContext
metaModelContext;
+
+ private MethodRemover_forTesting methodRemover;
+
+ @BeforeEach
+ protected void setup() {
+ setup(__->{});
+ }
+
+ protected final void setup(
+ final
Consumer<MetaModelContext_forTesting.MetaModelContext_forTestingBuilder>
customizer) {
+
+ var mockTranslationService = Mockito.mock(TranslationService.class);
+ var mockInteractionService = Mockito.mock(InteractionService.class);
+ var mockMemberExecutorService =
Mockito.mock(MemberExecutorService.class);
+
+ var iaContext = InteractionContextFactory.testing();
+
+ this.methodRemover = new MethodRemover_forTesting();
+
+ var builder = MetaModelContext_forTesting.builder()
+ .translationService(mockTranslationService)
+ .interactionService(mockInteractionService)
+ .memberExecutor(mockMemberExecutorService)
+ .valueSemantic(new IntValueSemantics());
+
+ customizer.accept(builder);
+
+ this.metaModelContext = builder.build();
+
+
Mockito.when(mockInteractionService.currentInteractionContext()).thenReturn(Optional.of(iaContext));
+ }
+
+ // -- TEAR DOWN
+
+ @AfterEach
+ protected void tearDownAll() {
+ methodRemover = null;
+ }
+
+ @FunctionalInterface
+ public static interface MemberScenarioConsumer {
+ void accept(
+ ProcessMethodContext processMethodContext,
+ FacetHolder facetHolder,
+ FacetedMethod facetedMethod);
+ }
+ @FunctionalInterface
+ protected static interface MixedInActionScenarioConsumer {
+ void accept(
+ ProcessMethodContext processMethodContext,
+ ObjectSpecification mixeeSpec,
+ FacetedMethod facetedMethod,
+ ObjectAction mixedInAct);
+ }
+ @FunctionalInterface
+ protected static interface MixedInPropertyScenarioConsumer {
+ void accept(
+ ProcessMethodContext processMethodContext,
+ ObjectSpecification mixeeSpec,
+ FacetedMethod facetedMethod,
+ OneToOneAssociation mixedInProp);
+ }
+ @FunctionalInterface
+ protected static interface MixedInCollectionScenarioConsumer {
+ void accept(
+ ProcessMethodContext processMethodContext,
+ ObjectSpecification mixeeSpec,
+ FacetedMethod facetedMethod,
+ OneToManyAssociation mixedInColl);
+ }
+ @FunctionalInterface
+ protected static interface ParameterScenarioConsumer {
+ void accept(
+ ProcessParameterContext processParameterContext,
+ FacetHolder facetHolder,
+ FacetedMethod facetedMethod,
+ FacetedMethodParameter facetedMethodParameter);
+ }
+
+ /**
+ * Action scenario.
+ */
+ protected void actionScenario(
+ final Class<?> declaringClass, final String actionName, final
MemberScenarioConsumer consumer) {
+ actionScenario(ActionScenario.builder(declaringClass,
actionName).build(), consumer);
+ }
+ /**
+ * Custom Action scenario.
+ */
+ protected void actionScenario(
+ final ActionScenario scenario, final MemberScenarioConsumer
consumer) {
+
+ var declaringClass = scenario.declaringClass();
+ var memberId = scenario.actionName();
+ var actionMethod = _Utils.findMethodByNameOrFail(declaringClass,
memberId);
+ var paramTypes = actionMethod.paramTypes();
+ var facetHolder = actionFacetHolder(declaringClass, memberId,
paramTypes);
+ var facetedMethod =
FacetedMethod.testing.createForAction(getMetaModelContext(), declaringClass,
memberId, paramTypes);
+ var processMethodContext = ProcessMethodContext
+ .forTesting(declaringClass, FeatureType.ACTION, actionMethod,
methodRemover, facetedMethod);
+
+ consumer.accept(processMethodContext, facetHolder, facetedMethod);
+ }
+ /**
+ * MixedIn Action scenario.
+ */
+ protected void actionScenarioMixedIn(
+ final Class<?> mixeeClass, final Class<?> mixinClass, final
MixedInActionScenarioConsumer consumer) {
+ var scenario = ActionScenario.builder(mixeeClass, "unused")
+ .mixinClass(Optional.of(mixinClass))
+ .build();
+ actionScenarioMixedIn(scenario, consumer);
+ }
+ protected void actionScenarioMixedIn(
+ final ActionScenario scenario, final MixedInActionScenarioConsumer
consumer) {
+
+ var declaringClass = scenario.declaringClass();
+
+ // get mixin main, assuming 'act'
+ var mixinClass = scenario.mixinClass().orElseThrow();
+ var mixedInMethod = _Utils.findMethodByNameOrFail(mixinClass, "act");
+
+ var annotatedMethod = mixedInMethod;
+ var facetedMethod =
FacetedMethod.createForAction(getMetaModelContext(), mixinClass,
+ _MethodFacades.regular(annotatedMethod));
+
+ var id = facetedMethod.getFeatureIdentifier();
+ assertNotNull(id.className());
+
+ var processMethodContext = new ProcessMethodContext(
+ mixinClass, IntrospectionPolicy.ENCAPSULATION_ENABLED,
FeatureType.ACTION,
+ _MethodFacades.regular(annotatedMethod),
+ methodRemover, facetedMethod, true);
+
+ final ObjectSpecification mixeeSpec =
getSpecificationLoader().loadSpecification(declaringClass);
+ final ObjectSpecification mixinSpec =
getSpecificationLoader().loadSpecification(mixinClass);
+ final ObjectAction mixedInAct =
+ _JUnitSupport.mixedInActionforMixinMain(mixeeSpec, mixinSpec,
"act", facetedMethod);
+
+ consumer.accept(processMethodContext, mixeeSpec, facetedMethod,
mixedInAct);
+ }
+
+ /**
+ * Parameter scenario.
+ */
+ protected void parameterScenario(
+ final Class<?> declaringClass, final String actionName, final int
paramIndex, final ParameterScenarioConsumer consumer) {
+ parameterScenario(ParameterScenario.builder(declaringClass,
actionName, paramIndex).build(), consumer);
+ }
+ /**
+ * Custom Parameter scenario.
+ */
+ protected void parameterScenario(
+ final ParameterScenario scenario, final ParameterScenarioConsumer
consumer) {
+ _Assert.assertEquals(0, scenario.paramIndex(), ()->"not yet
implemented otherwise");
+
+ var declaringClass = scenario.declaringClass();
+ var memberId = scenario.actionName();
+ var actionMethod = _Utils.findMethodByNameOrFail(declaringClass,
memberId);
+ var paramTypes = actionMethod.paramTypes();
+ var facetHolder = actionFacetHolder(declaringClass, memberId,
paramTypes);
+ var facetedMethod =
FacetedMethod.testing.createForAction(getMetaModelContext(), declaringClass,
memberId, paramTypes);
+ var facetedMethodParameter =
+ actionMethod.isNoArg()
+ ? (FacetedMethodParameter)null
+ : new FacetedMethodParameter(getMetaModelContext(),
+ FeatureType.ACTION_PARAMETER_SINGULAR,
facetedMethod.owningType(),
+ facetedMethod.methodFacade(), 0);
+
+ var processParameterContext =
+ FacetFactory.ProcessParameterContext.forTesting(
+ declaringClass,
IntrospectionPolicy.ANNOTATION_OPTIONAL, actionMethod, null,
facetedMethodParameter);
+
+ consumer.accept(processParameterContext, facetHolder, facetedMethod,
facetedMethodParameter);
+ }
+
+ /**
+ * Property scenario.
+ */
+ protected void propertyScenario(
+ final Class<?> declaringClass, final String propertyName, final
MemberScenarioConsumer consumer) {
+ propertyScenario(PropertyScenario.builder(declaringClass,
propertyName).build(), consumer);
+ }
+ /**
+ * Custom Property scenario.
+ */
+ protected void propertyScenario(
+ final PropertyScenario scenario, final MemberScenarioConsumer
consumer) {
+ var declaringClass = scenario.declaringClass();
+ var memberId = scenario.propertyName();
+
+ var facetHolder = propertyFacetHolder(declaringClass, memberId);
+ var annotatedMethod = _Utils.findGetterOrFail(declaringClass,
memberId);
+ var facetedMethod =
FacetedMethod.createForProperty(getMetaModelContext(), declaringClass,
annotatedMethod);
+
+ var processMethodContext = ProcessMethodContext
+ .forTesting(declaringClass, FeatureType.PROPERTY,
annotatedMethod, methodRemover, facetedMethod);
+
+ consumer.accept(processMethodContext, facetHolder, facetedMethod);
+ }
+ /**
+ * MixedIn Property scenario.
+ */
+ protected void propertyScenarioMixedIn(
+ final Class<?> mixeeClass, final Class<?> mixinClass, final
MixedInPropertyScenarioConsumer consumer) {
+ var scenario = PropertyScenario.builder(mixeeClass, "unused")
+ .mixinClass(Optional.of(mixinClass))
+ .build();
+ propertyScenarioMixedIn(scenario, consumer);
+ }
+ protected void propertyScenarioMixedIn(
+ final PropertyScenario scenario, final
MixedInPropertyScenarioConsumer consumer) {
+
+ var declaringClass = scenario.declaringClass();
+
+ // get mixin main, assuming 'prop'
+ var mixinClass = scenario.mixinClass().orElseThrow();
+ var annotatedMethod = _Utils.findMethodByNameOrFail(mixinClass,
"prop");
+
+ var facetedMethod =
FacetedMethod.createForProperty(getMetaModelContext(), mixinClass,
annotatedMethod);
+
+ var id = facetedMethod.getFeatureIdentifier();
+ assertNotNull(id.className());
+
+ var processMethodContext = new ProcessMethodContext(
+ mixinClass, IntrospectionPolicy.ENCAPSULATION_ENABLED,
FeatureType.PROPERTY,
+ _MethodFacades.regular(annotatedMethod),
+ methodRemover, facetedMethod, true);
+
+ final ObjectSpecification mixeeSpec =
getSpecificationLoader().loadSpecification(declaringClass);
+ final ObjectSpecification mixinSpec =
getSpecificationLoader().loadSpecification(mixinClass);
+ final OneToOneAssociation mixedInProp =
+ _JUnitSupport.mixedInProp(mixeeSpec, mixinSpec, "prop",
facetedMethod);
+
+ consumer.accept(processMethodContext, mixeeSpec, facetedMethod,
mixedInProp);
+ }
+
+ /**
+ * Collection scenario.
+ */
+ protected void collectionScenario(
+ final Class<?> declaringClass, final String collectionName, final
MemberScenarioConsumer consumer) {
+ collectionScenario(CollectionScenario.builder(declaringClass,
collectionName).build(), consumer);
+ }
+ /**
+ * Custom Collection scenario.
+ */
+ protected void collectionScenario(
+ final CollectionScenario scenario, final MemberScenarioConsumer
consumer) {
+
+ var declaringClass = scenario.declaringClass();
+ var memberId = scenario.collectionName();
+ var facetHolder = collectionFacetHolder(declaringClass, memberId);
+ var annotatedMethod = _Utils.findGetterOrFail(declaringClass,
memberId);
+ var facetedMethod =
FacetedMethod.createForProperty(getMetaModelContext(), declaringClass,
annotatedMethod);
+
+ var processMethodContext = ProcessMethodContext
+ .forTesting(declaringClass, FeatureType.COLLECTION,
annotatedMethod, methodRemover, facetedMethod);
+
+ consumer.accept(processMethodContext, facetHolder, facetedMethod);
+ }
+ /**
+ * MixedIn Collection scenario.
+ */
+ protected void collectionScenarioMixedIn(
+ final Class<?> mixeeClass, final Class<?> mixinClass, final
MixedInCollectionScenarioConsumer consumer) {
+ var scenario = CollectionScenario.builder(mixeeClass, "unused")
+ .mixinClass(Optional.of(mixinClass))
+ .build();
+ collectionScenarioMixedIn(scenario, consumer);
+ }
+ protected void collectionScenarioMixedIn(
+ final CollectionScenario scenario, final
MixedInCollectionScenarioConsumer consumer) {
+
+ var declaringClass = scenario.declaringClass();
+
+ // get mixin main, assuming 'coll'
+ var mixinClass = scenario.mixinClass().orElseThrow();
+ var annotatedMethod = _Utils.findMethodByNameOrFail(mixinClass,
"coll");
+
+ var facetedMethod =
FacetedMethod.createForCollection(getMetaModelContext(), mixinClass,
annotatedMethod);
+
+ var id = facetedMethod.getFeatureIdentifier();
+ assertNotNull(id.className());
+
+ var processMethodContext = new ProcessMethodContext(
+ mixinClass, IntrospectionPolicy.ENCAPSULATION_ENABLED,
FeatureType.COLLECTION,
+ _MethodFacades.regular(annotatedMethod),
+ methodRemover, facetedMethod, true);
+
+ final ObjectSpecification mixeeSpec =
getSpecificationLoader().loadSpecification(declaringClass);
+ final ObjectSpecification mixinSpec =
getSpecificationLoader().loadSpecification(mixinClass);
+ final OneToManyAssociation mixedInColl =
+ _JUnitSupport.mixedInColl(mixeeSpec, mixinSpec, "coll",
facetedMethod);
+
+ consumer.accept(processMethodContext, mixeeSpec, facetedMethod,
mixedInColl);
+ }
+
+ /**
+ * DomainObject scenario.
+ */
+ protected void objectScenario(final Class<?> declaringClass, final
BiConsumer<ProcessClassContext, FacetHolder> consumer) {
+ var facetHolder = FacetHolder.simple(getMetaModelContext(),
+ Identifier.classIdentifier(LogicalType.fqcn(declaringClass)));
+ var processClassContext = ProcessClassContext
+ .forTesting(declaringClass, methodRemover, facetHolder);
+ consumer.accept(processClassContext, facetHolder);
+ }
+
+ // -- UTILITY
+
+ protected static ResolvedMethod findMethodExactOrFail(final Class<?> type,
final String methodName, final Class<?>[] paramTypes) {
+ return _Utils.findMethodExactOrFail(type, methodName, paramTypes);
+ }
+
+ protected static ResolvedMethod findMethodExactOrFail(final Class<?> type,
final String methodName) {
+ return _Utils.findMethodExactOrFail(type, methodName);
+ }
+
+ protected static Optional<ResolvedMethod> findMethodExact(final Class<?>
type, final String methodName) {
+ return _Utils.findMethodExact(type, methodName);
+ }
+
+ private FacetHolder actionFacetHolder(final Class<?> declaringClass, final
String memberId, final Class<?>[] paramTypes) {
+ return FacetHolder.simple(getMetaModelContext(),
+ Identifier.actionIdentifier(LogicalType.fqcn(declaringClass),
memberId, paramTypes));
+ }
+
+ private FacetHolder propertyFacetHolder(final Class<?> declaringClass,
final String memberId) {
+ return FacetHolder.simple(getMetaModelContext(),
+
Identifier.propertyIdentifier(LogicalType.fqcn(declaringClass), memberId));
+ }
+
+ private FacetHolder collectionFacetHolder(final Class<?> declaringClass,
final String memberId) {
+ return FacetHolder.simple(getMetaModelContext(),
+
Identifier.collectionIdentifier(LogicalType.fqcn(declaringClass), memberId));
+ }
+
+ // -- EXPECTATIONS
+
+ protected final void assertNoMethodsRemoved() {
+ assertTrue(methodRemover.removedMethodMethodCalls().isEmpty());
+ assertTrue(methodRemover.removeMethodArgsCalls().isEmpty());
+ }
+
+ protected final void assertMethodWasRemoved(final ResolvedMethod method) {
+ assertTrue(methodRemover.removedMethodMethodCalls().contains(method),
+ ()->String.format("method was not removed in test scenario:
%s", method));
+ }
+
+ protected final void assertMethodWasRemoved(final Class<?> type, final
String methodName) {
+ assertMethodWasRemoved(findMethodExactOrFail(type, methodName));
+ }
+
+ protected final void assertMethodEqualsFirstIn(
+ final @NonNull ResolvedMethod method,
+ final @NonNull ImperativeFacet imperativeFacet) {
+ _Utils.assertMethodEquals(method,
imperativeFacet.getMethods().getFirstElseFail().asMethodElseFail());
+ }
+
+}
diff --git
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactoryTest.java
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactoryTest.java
new file mode 100644
index 00000000000..deb67cba2de
--- /dev/null
+++
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactoryTest.java
@@ -0,0 +1,768 @@
+/*
+ * 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.facets.object.domainobject;
+
+import java.util.UUID;
+
+import jakarta.inject.Named;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.springframework.boot.test.util.TestPropertyValues;
+
+import org.apache.causeway.applib.annotation.Bounding;
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.Publishing;
+import org.apache.causeway.applib.id.LogicalType;
+import org.apache.causeway.applib.mixins.system.HasInteractionId;
+import org.apache.causeway.commons.collections.Can;
+import
org.apache.causeway.core.config.metamodel.facets.DomainObjectConfigOptions;
+import org.apache.causeway.core.metamodel.facetapi.Facet;
+import org.apache.causeway.core.metamodel.facets.FacetFactoryTestAbstract;
+import
org.apache.causeway.core.metamodel.facets.object.autocomplete.AutoCompleteFacet;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.autocomplete.AutoCompleteFacetForDomainObjectAnnotation;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.choices.ChoicesFacetForDomainObjectAnnotation;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.editing.ImmutableFacetForDomainObjectAnnotation;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.editing.ImmutableFacetForDomainObjectAnnotationAsConfigured;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.editing.ImmutableFacetFromConfiguration;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.entitychangepublishing.EntityChangePublishingFacetForDomainObjectAnnotation;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.entitychangepublishing.EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured;
+import
org.apache.causeway.core.metamodel.facets.object.domainobject.entitychangepublishing.EntityChangePublishingFacetFromConfiguration;
+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.publish.entitychange.EntityChangePublishingFacet;
+import
org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacet;
+import
org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacetForDomainObjectAnnotation;
+import
org.apache.causeway.core.metamodel.facets.objectvalue.choices.ChoicesFacet;
+import
org.apache.causeway.core.metamodel.specloader.validator.ValidationFailures;
+import org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting;
+
+abstract class DomainObjectAnnotationFacetFactoryTest
+extends FacetFactoryTestAbstract {
+
+ DomainObjectAnnotationFacetFactory facetFactory;
+
+ @AfterEach
+ protected void tearDown() throws Exception {
+ facetFactory = null;
+ }
+
+ static class Customer {
+ }
+
+ class SomeHasInteractionId implements HasInteractionId {
+
+ @Override
+ public UUID getInteractionId() {
+ return null;
+ }
+
+ }
+
+ @Override
+ protected void setup() {
+ //overrides default setup method to do nothing
+ }
+
+ void allowingEntityChangePublishingToReturn(final
DomainObjectConfigOptions.EntityChangePublishingPolicy value) {
+ var testPropertyValues = value!=null
+ ?
TestPropertyValues.of("causeway.applib.annotation.domainObject.entityChangePublishing="
+ value.name())
+ : TestPropertyValues.empty();
+ super.setup(builder->builder.testPropertyValues(testPropertyValues));
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ }
+ void allowingObjectsEditingToReturn(final
DomainObjectConfigOptions.EditingObjectsConfiguration value) {
+ var testPropertyValues = value!=null
+ ?
TestPropertyValues.of("causeway.applib.annotation.domainObject.editing=" +
value.name())
+ : TestPropertyValues.empty();
+ super.setup(builder->builder.testPropertyValues(testPropertyValues));
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ }
+ protected void ignoringConfiguration() {
+ super.setup();
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ }
+
+ @Nested
+ public class EntityChangePublishing {
+
+ @DomainObject(entityChangePublishing =
org.apache.causeway.applib.annotation.Publishing.AS_CONFIGURED)
+ class CustomerWithDomainObjectAndAuditingSetToAsConfigured {
+ }
+
+ @DomainObject(entityChangePublishing =
org.apache.causeway.applib.annotation.Publishing.DISABLED)
+ class CustomerWithDomainObjectAndAuditingSetToDisabled {
+ }
+
+ @DomainObject(entityChangePublishing =
org.apache.causeway.applib.annotation.Publishing.ENABLED)
+ class CustomerWithDomainObjectAndAuditingSetToEnabled {
+ }
+
+ @DomainObject(entityChangePublishing =
Publishing.ENABLED_FOR_UPDATES_ONLY)
+ class
CustomerWithDomainObjectAndEntityChangePublishingSetToEnabledForUpdatesOnly {
+ }
+
+ @Nested
+ public class WhenNotAnnotatedAndDefaultsFromConfiguration {
+
+ @Test
+ void configured_value_set_to_all() {
+
allowingEntityChangePublishingToReturn(DomainObjectConfigOptions.EntityChangePublishingPolicy.ALL);
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.processEntityChangePublishing(
+
processClassContext.synthesizeOnType(DomainObject.class), processClassContext);
+
+ final EntityChangePublishingFacet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertThat(facet, is(notNullValue()));
+ assertTrue(facet instanceof
EntityChangePublishingFacetFromConfiguration);
+ assertThat(facet.isEnabled(), is(true));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ void configured_value_set_to_none() {
+
allowingEntityChangePublishingToReturn(DomainObjectConfigOptions.EntityChangePublishingPolicy.NONE);
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final EntityChangePublishingFacet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertNotNull(facet);
+ assertThat(facet.isEnabled(), is(false));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithAuditingSetToAsConfigured {
+
+ @Test
+ public void configured_value_set_to_all() {
+
allowingEntityChangePublishingToReturn(DomainObjectConfigOptions.EntityChangePublishingPolicy.ALL);
+
objectScenario(CustomerWithDomainObjectAndAuditingSetToAsConfigured.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final EntityChangePublishingFacet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
EntityChangePublishingFacetForDomainObjectAnnotationAsConfigured);
+ assertThat(facet.isEnabled(), is(true));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void configured_value_set_to_none() {
+
allowingEntityChangePublishingToReturn(DomainObjectConfigOptions.EntityChangePublishingPolicy.NONE);
+
objectScenario(CustomerWithDomainObjectAndAuditingSetToAsConfigured.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final EntityChangePublishingFacet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertNotNull(facet);
+ assertThat(facet.isEnabled(), is(false));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithAuditingSetToEnabled {
+
+ @Test
+ public void irrespective_of_configured_value() {
+ allowingEntityChangePublishingToReturn(null);
+
objectScenario(CustomerWithDomainObjectAndAuditingSetToEnabled.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
EntityChangePublishingFacetForDomainObjectAnnotation);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @Nested
+ public class
WithDomainObjectAnnotationWithEntityChangePublishingSetToEnabledForUpdatesOnly {
+
+ @Test
+ public void irrespective_of_configured_value() {
+ allowingEntityChangePublishingToReturn(null);
+
objectScenario(CustomerWithDomainObjectAndEntityChangePublishingSetToEnabledForUpdatesOnly.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(EntityChangePublishingFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
EntityChangePublishingFacetForDomainObjectAnnotation);
+
+
assertFalse(EntityChangePublishingFacet.isPublishingEnabledForCreate(facetHolder));
+
assertTrue(EntityChangePublishingFacet.isPublishingEnabledForUpdate(facetHolder));
+
assertFalse(EntityChangePublishingFacet.isPublishingEnabledForDelete(facetHolder));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithAuditingSetToDisabled {
+
+ @Test
+ public void irrespective_of_configured_value() {
+
allowingEntityChangePublishingToReturn(DomainObjectConfigOptions.EntityChangePublishingPolicy.ALL);
+
objectScenario(CustomerWithDomainObjectAndAuditingSetToDisabled.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+
assertFalse(EntityChangePublishingFacet.isPublishingEnabled(facetHolder));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+ }
+
+ @Nested
+ public class AutoComplete {
+
+ class CustomerRepository {
+ public String lookup(final String x) { return null; }
+ }
+
+ class CustomerRepositoryWithDefaultMethodName {
+ public String autoComplete(final String x) { return null; }
+ }
+
+ @DomainObject(autoCompleteRepository = CustomerRepository.class,
autoCompleteMethod = "lookup")
+ class CustomerWithDomainObjectAndAutoCompleteRepositoryAndAction {
+ }
+
+ @DomainObject(autoCompleteRepository =
CustomerRepositoryWithDefaultMethodName.class)
+ class CustomerWithDomainObjectAndAutoCompleteRepository {
+ }
+
+ @DomainObject
+ class CustomerWithDomainObjectButNoAutoCompleteRepository {
+ }
+
+ @BeforeEach
+ public void setUp() {
+ ignoringConfiguration();
+ }
+
+ @Test
+ public void whenDomainObjectAndAutoCompleteRepositoryAndAction() {
+
+
objectScenario(CustomerWithDomainObjectAndAutoCompleteRepositoryAndAction.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(AutoCompleteFacet.class);
+ assertNotNull(facet);
+
+ assertTrue(facet instanceof
AutoCompleteFacetForDomainObjectAnnotation);
+
+ final AutoCompleteFacetForDomainObjectAnnotation
autoCompleteFacet = (AutoCompleteFacetForDomainObjectAnnotation) facet;
+
+
assertThat(CustomerRepository.class.isAssignableFrom(autoCompleteFacet.getRepositoryClass()),
is(true));
+ assertThat(autoCompleteFacet.getActionName(), is("lookup"));
+
+ assertNoMethodsRemoved();
+ });
+
+ }
+
+ @Test
+ public void whenDomainObjectAndAutoCompleteRepository() {
+
+
objectScenario(CustomerWithDomainObjectAndAutoCompleteRepository.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(AutoCompleteFacet.class);
+ assertNotNull(facet);
+
+ assertTrue(facet instanceof
AutoCompleteFacetForDomainObjectAnnotation);
+
+ final AutoCompleteFacetForDomainObjectAnnotation
autoCompleteFacet = (AutoCompleteFacetForDomainObjectAnnotation) facet;
+
+
assertThat(CustomerRepositoryWithDefaultMethodName.class.isAssignableFrom(autoCompleteFacet.getRepositoryClass()),
is(true));
+ assertThat(autoCompleteFacet.getActionName(),
is("autoComplete"));
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenDomainObjectAnnotationButNoAutoComplete() {
+
+
objectScenario(CustomerWithDomainObjectButNoAutoCompleteRepository.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(AutoCompleteFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenNoDomainObjectAnnotation() {
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(AutoCompleteFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @Nested
+ public class Bounded {
+
+ @DomainObject(bounding = Bounding.BOUNDED)
+ class CustomerWithDomainObjectAndBoundedSetToTrue {
+ }
+
+ @DomainObject(bounding = Bounding.UNBOUNDED)
+ class CustomerWithDomainObjectAndBoundedSetToFalse {
+ }
+
+ @DomainObject
+ class CustomerWithDomainObjectButNoBounded {
+ }
+
+ @BeforeEach
+ public void setUp() {
+ ignoringConfiguration();
+ }
+
+ @Test
+ public void whenDomainObjectAndBoundedSetToTrue() {
+
+ objectScenario(CustomerWithDomainObjectAndBoundedSetToTrue.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ChoicesFacet.class);
+ assertNotNull(facet);
+
+ assertTrue(facet instanceof
ChoicesFacetForDomainObjectAnnotation);
+
+ assertNoMethodsRemoved();
+
+ });
+ }
+
+ @Test
+ public void whenDomainObjectAndAutoCompleteRepository() {
+
+ objectScenario(CustomerWithDomainObjectAndBoundedSetToFalse.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ChoicesFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenNoDomainObjectAnnotation() {
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ChoicesFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+ }
+
+
+ @DomainObject(editing =
org.apache.causeway.applib.annotation.Editing.AS_CONFIGURED)
+ static class CustomerWithDomainObjectAndEditingSetToAsConfigured {
+ }
+
+ @DomainObject(editing =
org.apache.causeway.applib.annotation.Editing.DISABLED)
+ static class CustomerWithDomainObjectAndEditingSetToDisabled {
+ }
+
+ @DomainObject(editing =
org.apache.causeway.applib.annotation.Editing.ENABLED)
+ static class CustomerWithDomainObjectAndEditingSetToEnabled {
+ }
+
+ @Nested
+ public class Editing {
+
+ @Nested
+ public class WhenNotAnnotatedAndDefaultsFromConfiguration {
+
+ @Test
+ public void configured_value_set_to_true() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.TRUE);
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+
+ });
+ }
+
+ @Test
+ public void configured_value_set_to_false() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.FALSE);
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
ImmutableFacetFromConfiguration);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void configured_value_set_to_defaults() {
+ ignoringConfiguration();
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNotNull(facet); // default is now non-editable
+ assertTrue(facet instanceof
ImmutableFacetFromConfiguration);
+
+ assertNoMethodsRemoved();
+ });
+ }
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithEditingSetToAsConfigured {
+
+ @Test
+ public void configured_value_set_to_true() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.TRUE);
+
objectScenario(CustomerWithDomainObjectAndEditingSetToAsConfigured.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void configured_value_set_to_false() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.FALSE);
+
objectScenario(CustomerWithDomainObjectAndEditingSetToAsConfigured.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
ImmutableFacetForDomainObjectAnnotation);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void configured_value_set_to_defaults() {
+ ignoringConfiguration();
+
objectScenario(CustomerWithDomainObjectAndEditingSetToAsConfigured.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNotNull(facet); // default is now non-editable
+ assertTrue(facet instanceof
ImmutableFacetForDomainObjectAnnotationAsConfigured);
+
+ assertNoMethodsRemoved();
+ });
+ }
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithEditingSetToEnabled {
+
+ @Test
+ public void irrespective_of_configured_value() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.FALSE);
+
objectScenario(CustomerWithDomainObjectAndEditingSetToEnabled.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final ImmutableFacet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+ }
+
+ @Nested
+ public class WithDomainObjectAnnotationWithEditingSetToDisabled {
+
+ @Test
+ public void irrespective_of_configured_value() {
+
allowingObjectsEditingToReturn(DomainObjectConfigOptions.EditingObjectsConfiguration.TRUE);
+
objectScenario(CustomerWithDomainObjectAndEditingSetToDisabled.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet =
facetHolder.getFacet(ImmutableFacet.class);
+ assertNotNull(facet);
+ assertTrue(facet instanceof
ImmutableFacetForDomainObjectAnnotation);
+
+ assertNoMethodsRemoved();
+ });
+ }
+ }
+ }
+
+ @Named("CUS")
+ @DomainObject
+ static class LogicalTypeNameCustomerWithDomainObjectAndObjectTypeSet {
+ }
+
+ @DomainObject
+ static class LogicalTypeNameCustomerWithDomainObjectButNoObjectType {
+ }
+
+ @Nested
+ public class LogicalTypeName {
+
+ @BeforeEach
+ public void setUp() {
+ ignoringConfiguration();
+ }
+
+ @Test
+ public void whenDomainObjectAndObjectTypeSetToTrue() {
+
assertThat(LogicalType.infer(LogicalTypeNameCustomerWithDomainObjectAndObjectTypeSet.class).logicalName(),
+ is("CUS"));
+ assertNoMethodsRemoved();
+ }
+
+ @Test
+ public void whenDomainObjectAndObjectTypeNotSet() {
+
+
objectScenario(LogicalTypeNameCustomerWithDomainObjectButNoObjectType.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(AliasedFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenNoDomainObjectAnnotation() {
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(AliasedFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ }
+
+ @DomainObject(nature = org.apache.causeway.applib.annotation.Nature.ENTITY)
+ static class CustomerWithDomainObjectAndNatureSetToJdoEntity {
+ }
+
+ @DomainObject(nature =
org.apache.causeway.applib.annotation.Nature.NOT_SPECIFIED)
+ static class CustomerWithDomainObjectAndNatureSetToNotSpecified {
+ }
+
+ @DomainObject(nature =
org.apache.causeway.applib.annotation.Nature.VIEW_MODEL)
+ static class CustomerWithDomainObjectAndNatureSetToViewModel {
+ }
+
+ @DomainObject
+ static class CustomerWithDomainObjectButNoNature {
+ }
+
+ @Nested
+ public class Nature {
+
+ @BeforeEach
+ public void setUp() {
+ ignoringConfiguration();
+ }
+
+ @Test
+ public void whenDomainObjectAndNatureSetToJdoEntity() {
+
+
objectScenario(CustomerWithDomainObjectAndNatureSetToJdoEntity.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ViewModelFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenDomainObjectAndNatureSetToNotSpecified() {
+
+
objectScenario(CustomerWithDomainObjectAndNatureSetToNotSpecified.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ViewModelFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+ }
+
+ @Test
+ public void whenDomainObjectAndNatureSetToViewModel() {
+
+
objectScenario(CustomerWithDomainObjectAndNatureSetToViewModel.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ViewModelFacet.class);
+ assertNotNull(facet);
+
+ assertTrue(facet instanceof
ViewModelFacetForDomainObjectAnnotation);
+
+ assertNoMethodsRemoved();
+ });
+
+ }
+
+ @Test
+ public void whenNoDomainObjectAnnotation() {
+
+
objectScenario(DomainObjectAnnotationFacetFactoryTest.Customer.class,
(processClassContext, facetHolder)->{
+ facetFactory.process(processClassContext);
+
+ final Facet facet = facetHolder.getFacet(ViewModelFacet.class);
+ assertNull(facet);
+
+ assertNoMethodsRemoved();
+ });
+
+ }
+
+ }
+
+ @Named("object.name")
+ @DomainObject(aliased = {"object.name", "object.alias"})
+ static class AliasDomainObjectWithAliases {
+ }
+
+ @Named("service.name")
+ @DomainService(aliased = {"service.name", "service.alias"})
+ static class AliasDomainServiceWithAliases {
+ }
+
+ @Nested
+ public class Alias {
+ DomainObjectAnnotationFacetFactory facetFactory;
+
+ @Test
+ public void testValidationDomainObjectWithAliasesConfigured() {
+ metaModelContext = MetaModelContext_forTesting.builder()
+
.testPropertyValues(TestPropertyValues.of("causeway.core.metaModel.validator.allowLogicalTypeNameAsAlias=true"))
+ .refiners(Can.of(__->facetFactory))
+ .build();
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ ((MetaModelContext_forTesting)
getMetaModelContext()).getProgrammingModel();//kicks off the programming model
factory
+
+
getMetaModelContext().getSpecificationLoader().specForTypeElseFail(AliasDomainObjectWithAliases.class);
+ ValidationFailures validationFailures =
getMetaModelContext().getSpecificationLoader().getOrAssessValidationResult();
+ assertFalse(validationFailures.hasFailures());
+ }
+
+ @Test
+ public void testValidationDomainServiceWithAliasesConfigured() {
+ metaModelContext = MetaModelContext_forTesting.builder()
+
.testPropertyValues(TestPropertyValues.of("causeway.core.metaModel.validator.allowLogicalTypeNameAsAlias=true"))
+ .refiners(Can.of(__->facetFactory))
+ .build();
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ ((MetaModelContext_forTesting)
getMetaModelContext()).getProgrammingModel();//kicks off the programming model
factory
+
+
getMetaModelContext().getSpecificationLoader().specForTypeElseFail(AliasDomainServiceWithAliases.class);
+ ValidationFailures validationFailures =
getMetaModelContext().getSpecificationLoader().getOrAssessValidationResult();
+ assertFalse(validationFailures.hasFailures());
+ }
+ @Test
+ public void testValidationDomainObjectWithAliasesDefault() {
+ metaModelContext = MetaModelContext_forTesting.builder()
+ .refiners(Can.of(__->facetFactory))
+ .build();
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ ((MetaModelContext_forTesting)
getMetaModelContext()).getProgrammingModel();//kicks off the programming model
factory
+
+
getMetaModelContext().getSpecificationLoader().specForTypeElseFail(AliasDomainObjectWithAliases.class);
+ ValidationFailures validationFailures =
getMetaModelContext().getSpecificationLoader().getOrAssessValidationResult();
+ assertTrue(validationFailures.hasFailures());
+ }
+
+ @Test
+ public void testValidationDomainServiceWithAliasesDefault() {
+ metaModelContext = MetaModelContext_forTesting.builder()
+ .refiners(Can.of(__->facetFactory))
+ .build();
+ facetFactory = new
DomainObjectAnnotationFacetFactory(getMetaModelContext());
+ ((MetaModelContext_forTesting)
getMetaModelContext()).getProgrammingModel();//kicks off the programming model
factory
+
+
getMetaModelContext().getSpecificationLoader().specForTypeElseFail(AliasDomainServiceWithAliases.class);
+ ValidationFailures validationFailures =
getMetaModelContext().getSpecificationLoader().getOrAssessValidationResult();
+ assertTrue(validationFailures.hasFailures());
+ }
+ }
+}
diff --git
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
new file mode 100644
index 00000000000..17bbf6dab49
--- /dev/null
+++
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactoryTest.java
@@ -0,0 +1,885 @@
+/*
+ * 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.facets.properties.property;
+
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.mockito.Mockito;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.MemberSupport;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Optionality;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.annotation.Publishing;
+import org.apache.causeway.applib.annotation.Snapshot;
+import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.applib.events.domain.PropertyDomainEvent;
+import org.apache.causeway.applib.spec.Specification;
+import org.apache.causeway.core.metamodel.commons.matchers.CausewayMatchers;
+import org.apache.causeway.core.metamodel.consent.Consent.VetoReason;
+import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.facetapi.FacetUtil;
+import
org.apache.causeway.core.metamodel.facets.DomainEventFacetAbstract.EventTypeOrigin;
+import org.apache.causeway.core.metamodel.facets.FacetFactory;
+import org.apache.causeway.core.metamodel.facets.FacetFactoryTestAbstract;
+import org.apache.causeway.core.metamodel.facets.FacetedMethod;
+import
org.apache.causeway.core.metamodel.facets.members.disabled.DisabledFacet;
+import
org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
+import
org.apache.causeway.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet;
+import
org.apache.causeway.core.metamodel.facets.objectvalue.mustsatisfyspec.MustSatisfySpecificationFacet;
+import org.apache.causeway.core.metamodel.facets.objectvalue.regex.RegExFacet;
+import
org.apache.causeway.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacetAbstract;
+import
org.apache.causeway.core.metamodel.facets.propcoll.memserexcl.SnapshotExcludeFacet;
+import
org.apache.causeway.core.metamodel.facets.properties.property.disabled.DisabledFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.property.entitychangepublishing.EntityPropertyChangePublishingPolicyFacet;
+import
org.apache.causeway.core.metamodel.facets.properties.property.mandatory.MandatoryFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.property.maxlength.MaxLengthFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.property.modify.PropertyDomainEventFacet;
+import
org.apache.causeway.core.metamodel.facets.properties.property.modify.PropertyModifyFacetAbstract;
+import
org.apache.causeway.core.metamodel.facets.properties.property.mustsatisfy.MustSatisfySpecificationFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.property.regex.RegExFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.property.snapshot.SnapshotExcludeFacetForPropertyAnnotation;
+import
org.apache.causeway.core.metamodel.facets.properties.update.clear.PropertyClearFacet;
+import
org.apache.causeway.core.metamodel.facets.properties.update.clear.PropertyClearFacetAbstract;
+import
org.apache.causeway.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
+import
org.apache.causeway.core.metamodel.facets.properties.update.modify.PropertySetterFacetAbstract;
+import org.apache.causeway.core.metamodel.object.ManagedObject;
+import
org.apache.causeway.core.metamodel.postprocessors.members.SynthesizeDomainEventsForMixinPostProcessor;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+class PropertyAnnotationFacetFactoryTest extends FacetFactoryTestAbstract {
+
+ PropertyAnnotationFacetFactory facetFactory;
+
+ private static void processDomainEvent(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processDomainEvent(processMethodContext, propertyIfAny);
+ }
+
+ private static void processOptional(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processOptional(processMethodContext, propertyIfAny);
+ }
+
+ private static void processRegEx(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processRegEx(processMethodContext, propertyIfAny);
+ }
+
+ private static void processEditing(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processEditing(processMethodContext, propertyIfAny);
+ }
+
+ private static void processMaxLength(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processMaxLength(processMethodContext, propertyIfAny);
+ }
+
+ private static void processMustSatisfy(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processMustSatisfy(processMethodContext, propertyIfAny);
+ }
+
+ private static void processSnapshot(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+ facetFactory.processSnapshot(processMethodContext, propertyIfAny);
+ }
+
+ private static void processEntityPropertyChangePublishing(
+ final PropertyAnnotationFacetFactory facetFactory, final
FacetFactory.ProcessMethodContext processMethodContext) {
+ var propertyIfAny = facetFactory.propertyIfAny(processMethodContext);
+
facetFactory.processEntityPropertyChangePublishing(processMethodContext,
propertyIfAny);
+ }
+
+ @BeforeEach
+ final void setUp() throws Exception {
+ facetFactory = new
PropertyAnnotationFacetFactory(getMetaModelContext());
+ }
+
+ @AfterEach
+ final void tearDown() throws Exception {
+ facetFactory = null;
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class Modify extends PropertyAnnotationFacetFactoryTest {
+
+ private void addGetterFacet(final FacetHolder holder) {
+ var mockOnType = Mockito.mock(ObjectSpecification.class);
+ FacetUtil.addFacet(new
PropertyOrCollectionAccessorFacetAbstract(mockOnType, holder) {
+ @Override
+ public Object getAssociationValueAsPojo(
+ final ManagedObject inObject,
+ final InteractionInitiatedBy interactionInitiatedBy) {
+ return null;
+ }
+ });
+ }
+
+ private void addSetterFacet(final FacetHolder holder) {
+ FacetUtil.addFacet(new PropertySetterFacetAbstract(holder) {
+ @Override
+ public ManagedObject setProperty(
+ final OneToOneAssociation owningAssociation,
+ final ManagedObject inObject,
+ final ManagedObject value,
+ final InteractionInitiatedBy interactionInitiatedBy) {
+ return inObject;
+ }
+ });
+ }
+
+ private void addClearFacet(final FacetHolder holder) {
+ FacetUtil.addFacet(new PropertyClearFacetAbstract(holder) {
+ @Override
+ public ManagedObject clearProperty(
+ final OneToOneAssociation owningProperty,
+ final ManagedObject targetAdapter,
+ final InteractionInitiatedBy interactionInitiatedBy) {
+ return targetAdapter;
+ }
+ });
+ }
+
+ private void assertHasPropertyDomainEventFacet(
+ final FacetedMethod facetedMethod,
+ final EventTypeOrigin eventTypeOrigin,
+ final Class<? extends PropertyDomainEvent<?,?>> eventType) {
+ var domainEventFacet =
facetedMethod.lookupFacet(PropertyDomainEventFacet.class).orElseThrow();
+ assertEquals(eventTypeOrigin,
domainEventFacet.getEventTypeOrigin());
+ assertThat(domainEventFacet.getEventType(),
CausewayMatchers.classEqualTo(eventType));
+
+ if(facetedMethod.methodFacade().getName().equals("prop"))
+ return; // skip further checks, when in a mixed-in scenario
+
+ // then
+ var setterFacet =
facetedMethod.getFacet(PropertySetterFacet.class);
+ assertNotNull(setterFacet);
+ assertTrue(setterFacet instanceof PropertyModifyFacetAbstract,
"unexpected facet: " + setterFacet);
+ final PropertyModifyFacetAbstract setterFacetImpl =
(PropertyModifyFacetAbstract) setterFacet;
+ assertEquals(eventTypeOrigin,
setterFacetImpl.getEventTypeOrigin());
+ assertThat(setterFacetImpl.getEventType(),
CausewayMatchers.classEqualTo(eventType));
+
+ // then
+ var clearFacet = facetedMethod.getFacet(PropertyClearFacet.class);
+ assertNotNull(clearFacet);
+ assertTrue(clearFacet instanceof PropertyModifyFacetAbstract);
+ final PropertyModifyFacetAbstract clearFacetImpl =
(PropertyModifyFacetAbstract) clearFacet;
+ assertEquals(eventTypeOrigin,
setterFacetImpl.getEventTypeOrigin());
+ assertThat(clearFacetImpl.getEventType(),
CausewayMatchers.classEqualTo(eventType));
+ }
+
+ @Test
+ void withPropertyDomainEvent_fallingBackToDefault() {
+
+ class Customer {
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ addGetterFacet(facetedMethod);
+ addSetterFacet(facetedMethod);
+ addClearFacet(facetedMethod);
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.DEFAULT,
PropertyDomainEvent.Default.class);
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_annotatedOnMethod() {
+
+ class Customer {
+ class NamedChangedDomainEvent extends
PropertyDomainEvent<Customer, String> {}
+ @Property(domainEvent = NamedChangedDomainEvent.class)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ addGetterFacet(facetedMethod);
+ addSetterFacet(facetedMethod);
+ addClearFacet(facetedMethod);
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent.class);
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_annotatedOnType() {
+
+ @DomainObject(propertyDomainEvent =
Customer.NamedChangedDomainEvent.class)
+ class Customer {
+ class NamedChangedDomainEvent extends
PropertyDomainEvent<Customer, String> {}
+ @Property
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ addGetterFacet(facetedMethod);
+ addSetterFacet(facetedMethod);
+ addClearFacet(facetedMethod);
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_OBJECT,
Customer.NamedChangedDomainEvent.class);
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_annotatedOnTypeAndMethod() {
+
+ @DomainObject(propertyDomainEvent =
Customer.NamedChangedDomainEvent1.class)
+ class Customer {
+ class NamedChangedDomainEvent1 extends
PropertyDomainEvent<Customer, String> {}
+ class NamedChangedDomainEvent2 extends
PropertyDomainEvent<Customer, String> {}
+ @Property(domainEvent = NamedChangedDomainEvent2.class)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ addGetterFacet(facetedMethod);
+ addSetterFacet(facetedMethod);
+ addClearFacet(facetedMethod);
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+
+ // then - the property annotation should win
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent2.class);
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_mixedIn_annotatedOnMethod() {
+ var postProcessor = new
SynthesizeDomainEventsForMixinPostProcessor(getMetaModelContext());
+
+ // given
+ class Customer {
+ class NamedChangedDomainEvent extends
PropertyDomainEvent<Customer, String> {}
+ }
+ @DomainObject(nature=Nature.MIXIN, mixinMethod = "prop")
+ @RequiredArgsConstructor
+ @SuppressWarnings("unused")
+ class Customer_name {
+ final Customer mixee;
+ @Property(domainEvent = Customer.NamedChangedDomainEvent.class)
+ public String prop() { return "mixed-in name"; }
+ }
+
+ propertyScenarioMixedIn(Customer.class, Customer_name.class,
+ (processMethodContext, mixeeSpec, facetedMethod,
mixedInProp)->{
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+ postProcessor.postProcessProperty(mixeeSpec, mixedInProp);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent.class);
+
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_mixedIn_annotatedOnMixedInType() {
+ var postProcessor = new
SynthesizeDomainEventsForMixinPostProcessor(getMetaModelContext());
+
+ // given
+ class Customer {
+ class NamedChangedDomainEvent extends
PropertyDomainEvent<Customer, String> {}
+ }
+ @Property(domainEvent = Customer.NamedChangedDomainEvent.class)
+ @RequiredArgsConstructor
+ @SuppressWarnings("unused")
+ class Customer_name {
+ final Customer mixee;
+ @MemberSupport
+ public String prop() { return "mixed-in name"; }
+ }
+
+ propertyScenarioMixedIn(Customer.class, Customer_name.class,
+ (processMethodContext, mixeeSpec, facetedMethod,
mixedInProp)->{
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+ postProcessor.postProcessProperty(mixeeSpec, mixedInProp);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent.class);
+
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_mixedIn_annotatedOnMixeeType() {
+ var postProcessor = new
SynthesizeDomainEventsForMixinPostProcessor(getMetaModelContext());
+
+ // given
+ @DomainObject(propertyDomainEvent =
Customer.NamedChangedDomainEvent.class)
+ class Customer {
+ class NamedChangedDomainEvent extends
PropertyDomainEvent<Customer, String> {}
+ }
+ @Property
+ @RequiredArgsConstructor
+ @SuppressWarnings("unused")
+ class Customer_name {
+ final Customer mixee;
+ @MemberSupport
+ public String prop() { return "mixed-in name"; }
+ }
+
+ propertyScenarioMixedIn(Customer.class, Customer_name.class,
+ (processMethodContext, mixeeSpec, facetedMethod,
mixedInProp)->{
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+ postProcessor.postProcessProperty(mixeeSpec, mixedInProp);
+
+ // then
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_OBJECT,
Customer.NamedChangedDomainEvent.class);
+
+ });
+ }
+
+ @Test
+ void withPropertyDomainEvent_mixedIn_annotatedOnMixeeAndMixedInType() {
+ var postProcessor = new
SynthesizeDomainEventsForMixinPostProcessor(getMetaModelContext());
+
+ // given
+ @DomainObject(propertyDomainEvent =
Customer.NamedChangedDomainEvent1.class)
+ class Customer {
+ class NamedChangedDomainEvent1 extends
PropertyDomainEvent<Customer, String> {}
+ class NamedChangedDomainEvent2 extends
PropertyDomainEvent<Customer, String> {}
+ }
+ @Property(domainEvent = Customer.NamedChangedDomainEvent2.class)
+ @RequiredArgsConstructor
+ @SuppressWarnings("unused")
+ class Customer_name {
+ final Customer mixee;
+ @MemberSupport
+ public String prop() { return "mixed-in name"; }
+ }
+
+ propertyScenarioMixedIn(Customer.class, Customer_name.class,
+ (processMethodContext, mixeeSpec, facetedMethod,
mixedInProp)->{
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+ postProcessor.postProcessProperty(mixeeSpec, mixedInProp);
+
+ // then - the mixed-in annotation should win
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent2.class);
+
+ });
+ }
+
+ @Test
+ void
withPropertyDomainEvent_mixedIn_annotatedOnMixeeTypeAndMixedInMethod() {
+ var postProcessor = new
SynthesizeDomainEventsForMixinPostProcessor(getMetaModelContext());
+
+ // given
+ @DomainObject(propertyDomainEvent =
Customer.NamedChangedDomainEvent1.class)
+ class Customer {
+ class NamedChangedDomainEvent1 extends
PropertyDomainEvent<Customer, String> {}
+ class NamedChangedDomainEvent2 extends
PropertyDomainEvent<Customer, String> {}
+ }
+ @DomainObject(nature=Nature.MIXIN, mixinMethod = "prop")
+ @RequiredArgsConstructor
+ @SuppressWarnings("unused")
+ class Customer_name {
+ final Customer mixee;
+ @Property(domainEvent =
Customer.NamedChangedDomainEvent2.class)
+ public String prop() { return "mixed-in name"; }
+ }
+
+ propertyScenarioMixedIn(Customer.class, Customer_name.class,
+ (processMethodContext, mixeeSpec, facetedMethod,
mixedInProp)->{
+
+ // when
+ processDomainEvent(facetFactory, processMethodContext);
+ postProcessor.postProcessProperty(mixeeSpec, mixedInProp);
+
+ // then - the mixed-in annotation should win
+ assertHasPropertyDomainEventFacet(facetedMethod,
+ EventTypeOrigin.ANNOTATED_MEMBER,
Customer.NamedChangedDomainEvent2.class);
+
+ });
+ }
+
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class Editing extends PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void withAnnotationOnGetter() {
+ @SuppressWarnings("unused")
+ class Customer {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "you cannot edit the name
property")
+ public String getName() { return null; }
+ public void setName(final String name) {}
+ }
+
+ assertDisabledFacetOn(Customer.class, "name",
+ "you cannot edit the name property");
+ }
+
+ @Test
+ void withAnnotationOnField() {
+
+ class Customer {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "you cannot edit the name
property")
+ @Getter @Setter
+ private String name;
+ }
+
+ assertDisabledFacetOn(Customer.class, "name",
+ "you cannot edit the name property");
+ }
+
+ @Test
+ void withAnnotationOnBooleanGetter() {
+ @SuppressWarnings("unused")
+ class Customer {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "you cannot edit the
subscribed property"
+ )
+ public boolean isSubscribed() { return true; }
+ public void setSubscribed(final boolean b) {}
+ }
+
+ assertDisabledFacetOn(Customer.class, "subscribed",
+ "you cannot edit the subscribed property");
+ }
+
+ @Test
+ void withAnnotationOnBooleanField() {
+
+ class Customer {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "you cannot edit the
subscribed property"
+ )
+ @Getter @Setter
+ private boolean subscribed;
+ }
+
+ assertDisabledFacetOn(Customer.class, "subscribed",
+ "you cannot edit the subscribed property");
+ }
+
+ // -- SPECIAL SCENARIO CAUSEWAY-2963
+
+ static interface PrimitiveBooleanHolder {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "a")
+ boolean isReadWriteProperty();
+ void setReadWriteProperty(boolean c);
+ }
+
+ static class PrimitiveBooleanEntity implements PrimitiveBooleanHolder {
+ @Property(
+ editing =
org.apache.causeway.applib.annotation.Editing.DISABLED,
+ editingDisabledReason = "b")
+ @Getter @Setter
+ private boolean readWriteProperty;
+ }
+
+ @Test
+ void recognizeAnnotationOnPrimitiveBoolean() {
+ assertDisabledFacetOn(PrimitiveBooleanEntity.class,
"readWriteProperty", "b");
+ }
+
+ // -- HELPER
+
+ private void assertDisabledFacetOn(final Class<?> declaringClass,
final String propertyName, final String expectedDisabledReason) {
+
+ // given
+ propertyScenario(declaringClass, propertyName,
(processMethodContext, facetHolder, facetedMethod)->{
+ // when
+ processEditing(facetFactory, processMethodContext);
+ // then
+ var disabledFacet =
facetedMethod.getFacet(DisabledFacet.class);
+ assertNotNull(disabledFacet);
+ assertTrue(disabledFacet instanceof
DisabledFacetForPropertyAnnotation);
+ var disabledFacet2 = (DisabledFacetForPropertyAnnotation)
disabledFacet;
+ assertThat(disabledFacet.where(), is(Where.EVERYWHERE));
+
assertThat(disabledFacet2.disabledReason(null).map(VetoReason::string).orElse(null),
is(expectedDisabledReason));
+ });
+ }
+
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class MaxLength extends PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void withAnnotation() {
+
+ class Customer {
+ @Property(maxLength = 30)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processMaxLength(facetFactory, processMethodContext);
+
+ // then
+ final MaxLengthFacet maxLengthFacet =
facetedMethod.getFacet(MaxLengthFacet.class);
+ assertNotNull(maxLengthFacet);
+ assertTrue(maxLengthFacet instanceof
MaxLengthFacetForPropertyAnnotation);
+ assertThat(maxLengthFacet.value(), is(30));
+ });
+ }
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class MustSatisfy extends PropertyAnnotationFacetFactoryTest {
+
+ public static class NotTooHot implements Specification {
+ @Override
+ public String satisfies(final Object obj) {
+ return null;
+ }
+ }
+
+ public static class NotTooCold implements Specification {
+ @Override
+ public String satisfies(final Object obj) {
+ return null;
+ }
+ }
+
+ @Test
+ void withAnnotation() {
+
+ class Customer {
+ @Property(
+ mustSatisfy = {NotTooHot.class, NotTooCold.class}
+ )
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processMustSatisfy(facetFactory, processMethodContext);
+
+ // then
+ final MustSatisfySpecificationFacet
mustSatisfySpecificationFacet =
facetedMethod.getFacet(MustSatisfySpecificationFacet.class);
+ assertNotNull(mustSatisfySpecificationFacet);
+ assertTrue(mustSatisfySpecificationFacet instanceof
MustSatisfySpecificationFacetForPropertyAnnotation);
+ final MustSatisfySpecificationFacetForPropertyAnnotation
mustSatisfySpecificationFacetImpl =
(MustSatisfySpecificationFacetForPropertyAnnotation)
mustSatisfySpecificationFacet;
+ var specifications =
mustSatisfySpecificationFacetImpl.getSpecifications();
+ assertThat(specifications.size(), is(2));
+
+ assertTrue(specifications.getElseFail(0) instanceof NotTooHot);
+ assertTrue(specifications.getElseFail(1) instanceof
NotTooCold);
+ });
+ }
+
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class EntityPropertyChangePublishingPolicy extends
PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void exclusion() {
+
+ class Customer {
+ @Property(entityChangePublishing = Publishing.DISABLED)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processEntityPropertyChangePublishing(facetFactory,
processMethodContext);
+ // then
+ var changePolicyFacet =
facetedMethod.lookupFacet(EntityPropertyChangePublishingPolicyFacet.class).orElse(null);
+ assertNotNull(changePolicyFacet);
+ assertTrue(changePolicyFacet.isPublishingVetoed());
+ assertFalse(changePolicyFacet.isPublishingAllowed());
+ });
+ }
+
+ @Test
+ void whenDefault() {
+
+ class Customer {
+ @Property
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processEntityPropertyChangePublishing(facetFactory,
processMethodContext);
+ // then
+ var changePolicyFacet =
facetedMethod.getFacet(EntityPropertyChangePublishingPolicyFacet.class);
+ assertNull(changePolicyFacet);
+ });
+ }
+
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class SnapshotExcluded extends PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void withAnnotation() {
+
+ class Customer {
+ @Property(snapshot = Snapshot.EXCLUDED)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processSnapshot(facetFactory, processMethodContext);
+ // then
+ final SnapshotExcludeFacet snapshotExcludeFacet =
facetedMethod.getFacet(SnapshotExcludeFacet.class);
+ assertNotNull(snapshotExcludeFacet);
+ assertTrue(snapshotExcludeFacet instanceof
SnapshotExcludeFacetForPropertyAnnotation);
+ });
+ }
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class Mandatory extends PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void whenOptionalityIsTrue() {
+
+ class Customer {
+ @Property(optionality = Optionality.OPTIONAL)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processOptional(facetFactory, processMethodContext);
+ // then
+ final MandatoryFacet mandatoryFacet =
facetedMethod.getFacet(MandatoryFacet.class);
+ assertNotNull(mandatoryFacet);
+ assertTrue(mandatoryFacet instanceof
MandatoryFacetForPropertyAnnotation.Optional);
+ });
+ }
+
+ @Test
+ void whenOptionalityIsFalse() {
+
+ class Customer {
+ @Property(optionality = Optionality.MANDATORY)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processOptional(facetFactory, processMethodContext);
+ // then
+ final MandatoryFacet mandatoryFacet =
facetedMethod.getFacet(MandatoryFacet.class);
+ assertNotNull(mandatoryFacet);
+ assertTrue(mandatoryFacet instanceof
MandatoryFacetForPropertyAnnotation.Required);
+ });
+ }
+
+ @Test
+ void whenOptionalityIsDefault() {
+
+ class Customer {
+ @Property(optionality = Optionality.DEFAULT)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processOptional(facetFactory, processMethodContext);
+ // then
+ final MandatoryFacet mandatoryFacet =
facetedMethod.getFacet(MandatoryFacet.class);
+ assertNull(mandatoryFacet);
+ });
+ }
+
+ @Test
+ void whenNone() {
+
+ class Customer {
+ @Property()
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processOptional(facetFactory, processMethodContext);
+ // then
+ final MandatoryFacet mandatoryFacet =
facetedMethod.getFacet(MandatoryFacet.class);
+ assertNull(mandatoryFacet);
+ });
+ }
+
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class RegEx extends PropertyAnnotationFacetFactoryTest {
+
+ @Test
+ void whenHasAnnotation() {
+
+ class Customer {
+ @Property(
+ regexPattern = "[123].*",
+ regexPatternFlags = Pattern.CASE_INSENSITIVE |
Pattern.MULTILINE)
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processRegEx(facetFactory, processMethodContext);
+ // then
+ final RegExFacet regExFacet =
facetedMethod.getFacet(RegExFacet.class);
+ assertNotNull(regExFacet);
+ assertTrue(regExFacet instanceof
RegExFacetForPropertyAnnotation);
+ assertThat(regExFacet.patternFlags(), is(10));
+ assertThat(regExFacet.regexp(), is("[123].*"));
+ });
+ }
+
+ @Test
+ void whenNone() {
+
+ class Customer {
+ @Property()
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processRegEx(facetFactory, processMethodContext);
+ // then
+ final RegExFacet regExFacet =
facetedMethod.getFacet(RegExFacet.class);
+ assertNull(regExFacet);
+ });
+ }
+
+ @Test
+ void whenEmptyString() {
+
+ class Customer {
+ @Property(regexPattern = "")
+ @Getter @Setter private String name;
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processRegEx(facetFactory, processMethodContext);
+
+ // then
+ final RegExFacet regExFacet =
facetedMethod.getFacet(RegExFacet.class);
+ assertNull(regExFacet);
+ });
+ }
+
+ @Test
+ void whenNotAnnotatedOnStringProperty() {
+
+ class Customer {
+ @Property(regexPattern = "[abc].*")
+ public int getName() {return 0; }
+ @SuppressWarnings("unused") public void setName(final int
name) { }
+ }
+
+ // given
+ propertyScenario(Customer.class, "name", (processMethodContext,
facetHolder, facetedMethod)->{
+ // when
+ processRegEx(facetFactory, processMethodContext);
+
+ // then
+ final RegExFacet regExFacet =
facetedMethod.getFacet(RegExFacet.class);
+ assertNull(regExFacet);
+ });
+ }
+
+ }
+
+}
diff --git
a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
index e2baafec544..fce90ce2432 100644
---
a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
+++
b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
@@ -39,18 +39,12 @@
import jakarta.inject.Named;
import jakarta.inject.Provider;
-import org.apache.causeway.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
-import org.apache.causeway.core.metamodel.execution.InteractionInternal;
-
-import org.apache.causeway.core.metamodel.services.deadlock.DeadlockRecognizer;
-import org.apache.causeway.schema.chg.v2.ChangesDto;
-import org.apache.causeway.schema.chg.v2.ObjectsDto;
-import org.apache.causeway.schema.common.v2.OidsDto;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.annotation.Qualifier;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.core.Ordered;
-import org.jspecify.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -61,6 +55,7 @@
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.annotation.Programmatic;
import org.apache.causeway.applib.annotation.TransactionScope;
+import org.apache.causeway.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.applib.services.iactn.InteractionProvider;
@@ -74,10 +69,12 @@
import org.apache.causeway.commons.internal.collections._Sets;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.metamodel.execution.InteractionInternal;
import
org.apache.causeway.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.object.ManagedObjects;
import org.apache.causeway.core.metamodel.object.MmEntityUtils;
+import org.apache.causeway.core.metamodel.services.deadlock.DeadlockRecognizer;
import
org.apache.causeway.core.metamodel.services.objectlifecycle.HasEnlistedEntityPropertyChanges;
import
org.apache.causeway.core.metamodel.services.objectlifecycle.PreAndPostValue;
import
org.apache.causeway.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
@@ -88,9 +85,11 @@
import
org.apache.causeway.core.transaction.changetracking.EntityPropertyChangePublisher;
import
org.apache.causeway.core.transaction.changetracking.HasEnlistedEntityChanges;
import
org.apache.causeway.persistence.commons.CausewayModulePersistenceCommons;
+import org.apache.causeway.schema.chg.v2.ChangesDto;
+import org.apache.causeway.schema.chg.v2.ObjectsDto;
+import org.apache.causeway.schema.common.v2.OidsDto;
import lombok.Getter;
-import org.jspecify.annotations.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@@ -305,21 +304,18 @@ private boolean shouldPublish(final PreAndPostValue
preAndPostValue) {
private boolean isEntityExcludedForChangePublishing(final ManagedObject
entity) {
- if (!configuration.isEnabled()) {
+ if (!configuration.isEnabled())
return true;
- }
- if(!EntityChangePublishingFacet.isPublishingEnabled(entity.objSpec()))
{
+ if(!EntityChangePublishingFacet.isPublishingEnabled(entity.objSpec()))
return true; // ignore entities that are not enabled for entity
change publishing
- }
// guard against transient
if(ManagedObjects.bookmark(entity).isEmpty()) return true;
- if(changes.isMemoized()) {
+ if(changes.isMemoized())
throw _Exceptions.illegalState("Cannot enlist additional changes
for auditing, "
+ "since changedObjectPropertiesRef was already prepared
(memoized) for auditing.");
- }
return false;
}
@@ -373,9 +369,8 @@ public Optional<EntityChanges> getEntityChanges(
// a defensive copy of
var changeKindByEnlistedAdapter = new
HashMap<>(this.changeKindByEnlistedAdapter);
- if(changeKindByEnlistedAdapter.isEmpty()) {
+ if(changeKindByEnlistedAdapter.isEmpty())
return Optional.empty();
- }
final Interaction interaction = currentInteraction();
final int numberEntitiesLoaded1 = numberEntitiesLoaded();
@@ -418,9 +413,8 @@ private static ChangesDto newDto(
changeKindByEnlistedEntity.forEach((bookmark, kind)->{
var oidDto = bookmark.toOidDto();
- if(oidDto==null) {
+ if(oidDto==null)
return;
- }
switch(kind) {
case CREATE:
objectsDto.getCreated().getOid().add(oidDto);
@@ -530,9 +524,9 @@ public void enlistCreated(final ManagedObject entity) {
_Xray.enlistCreated(entity, interactionProviderProvider);
- if (isEntityExcludedForChangePublishing(entity)) {
+ if (isEntityExcludedForChangePublishing(entity)
+ ||
!EntityChangePublishingFacet.isPublishingEnabledForCreate(entity.objSpec()))
return;
- }
log.debug("enlist entity's property changes for publishing {}",
entity);
@@ -551,9 +545,9 @@ public void enlistUpdating(
_Xray.enlistUpdating(entity, interactionProviderProvider);
- if (isEntityExcludedForChangePublishing(entity)) {
+ if (isEntityExcludedForChangePublishing(entity)
+ ||
!EntityChangePublishingFacet.isPublishingEnabledForUpdate(entity.objSpec()))
return;
- }
if(log.isDebugEnabled()) {
log.debug("enlist entity's property changes for publishing {}",
entity);
@@ -588,7 +582,9 @@ public void enlistDeleting(final ManagedObject entity) {
_Xray.enlistDeleting(entity, interactionProviderProvider);
- if (isEntityExcludedForChangePublishing(entity)) return;
+ if (isEntityExcludedForChangePublishing(entity)
+ ||
!EntityChangePublishingFacet.isPublishingEnabledForDelete(entity.objSpec()))
+ return;
suppressAutoFlushIfRequired(() -> {
final boolean enlisted = enlistForChangeKindPublishing(entity,
EntityChangeKind.DELETE);