You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/06/28 06:47:29 UTC
(causeway) branch CAUSEWAY-4042 created (now 23486ad47de)
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a change to branch CAUSEWAY-4042
in repository https://gitbox.apache.org/repos/asf/causeway.git
at 23486ad47de CAUSEWAY-4042 : adds checks to service, new config property
This branch includes the following new commits:
new 23486ad47de CAUSEWAY-4042 : adds checks to service, new config property
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(causeway) 01/01: CAUSEWAY-4042 : adds checks to service, new config property
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-4042
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 23486ad47ded89103e21260106c7624cfef6ecda
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Jun 28 07:47:22 2026 +0100
CAUSEWAY-4042 : adds checks to service, new config property
---
.../core/config/CausewayConfiguration.java | 58 ++++++++++++++--
.../metamodel/consent/InteractionResultSet.java | 2 +-
.../command/CommandExecutorServiceDefault.java | 81 +++++++++++++++++++++-
3 files changed, 131 insertions(+), 10 deletions(-)
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
index 1e068123f4e..3236cf566f4 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
@@ -58,6 +58,8 @@
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
+import org.apache.causeway.applib.services.wrapper.control.ExceptionHandler;
+import org.apache.causeway.applib.services.wrapper.control.SyncControl;
import org.apache.causeway.applib.value.semantics.TemporalValueSemantics.TemporalDisplayPattern;
import org.springframework.beans.factory.annotation.Autowired;
@@ -2010,6 +2012,50 @@ public boolean isEnabled() {
}
}
+ private final CommandExecutorService commandExecutorService = new CommandExecutorService();
+ @Data
+ public static class CommandExecutorService {
+
+ /**
+ * Whether and how to interact with interaction advisors (to check if the member is hidden, disabled, valid)
+ */
+ public enum InteractionAdvisorPolicy {
+ /**
+ * Check with interaction advisors, throw an exception if there is a veto (similar to the
+ * {@link org.apache.causeway.applib.services.wrapper.WrapperFactory#wrap(Object)}.
+ *
+ * <p>
+ * The exceptions thrown are the same as those used by
+ * {@link org.apache.causeway.applib.services.wrapper.WrapperFactory}.
+ * </p>
+ *
+ * @see org.apache.causeway.applib.services.wrapper.WrapperFactory
+ */
+ CHECK,
+ /**
+ * Check with interaction advisors, but ignore any vetos.
+ *
+ * <p>
+ * This ensures that the event phases are called in the correct order, in case these are being
+ * leveraged for advanced processing (eg using the
+ * {@link org.apache.causeway.applib.services.scratchpad.Scratchpad}), but ignores any veto.
+ * </p>
+ */
+ CHECK_BUT_IGNORE,
+ /**
+ * Check with interaction advisors, but ignore any vetos (similar to the
+ * {@link org.apache.causeway.applib.services.wrapper.WrapperFactory#wrap(Object, SyncControl)} with
+ * {@link SyncControl#withSkipRules()}.
+ */
+ NO_CHECK
+ }
+
+ /**
+ * Whether and how to check any interaction advisors of the action/property (hide, disable, validate)
+ */
+ private InteractionAdvisorPolicy interactionAdvisorPolicy = InteractionAdvisorPolicy.NO_CHECK;
+ }
+
private final ApplicationFeatures applicationFeatures = new ApplicationFeatures();
@Data
public static class ApplicationFeatures {
@@ -2252,7 +2298,7 @@ public enum SafeguardMode {
REQUIRE_WEAVED
}
private SafeguardMode safeguardMode = SafeguardMode.REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED;
-
+
}
}
@@ -2958,7 +3004,7 @@ public static class Wicket {
* (typically trailing) column (labeled 'action-column').
*/
private boolean actionColumnEnabled = true;
-
+
/**
* Specifies the subclass of
* <code>org.apache.causeway.viewer.wicket.viewer.wicketapp.CausewayWicketApplication</code> that is used to
@@ -3391,17 +3437,17 @@ public static class DevelopmentUtilities {
@Data
public static class FileUpload {
/**
- * If left empty, the default allows ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'],
+ * If left empty, the default allows ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'],
* where 'object' enables fallback behavior. We remove this here.
- *
+ *
* @see https://plugins.krajee.com/file-input/plugin-options#disabledPreviewTypes
*/
private List<String> disabledPreviewTypes = List.of("object");
/**
* Some mime types can trigger unwanted download behavior, dependent on browser and or OS settings.
- *
+ *
* <p>We have seen CSV files causing issues, so we disallow those by default.
- *
+ *
* @see https://plugins.krajee.com/file-input/plugin-options#disabledPreviewMimeTypes
*/
private List<String> disabledPreviewMimeTypes = List.of("text/csv");
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java
index c8716d4029f..4b9f20b6a2a 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java
@@ -24,7 +24,7 @@
public class InteractionResultSet {
- private final List<InteractionResult> results = new ArrayList<InteractionResult>();
+ private final List<InteractionResult> results = new ArrayList<>();
private InteractionResult firstResult = null;
public InteractionResultSet() {
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
index 38a639228d1..487e7a3b0bb 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/command/CommandExecutorServiceDefault.java
@@ -30,8 +30,24 @@
import javax.inject.Named;
import javax.inject.Provider;
+import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.applib.services.wrapper.DisabledException;
+import org.apache.causeway.applib.services.wrapper.HiddenException;
+import org.apache.causeway.applib.services.wrapper.InvalidException;
+import org.apache.causeway.applib.services.wrapper.events.ActionArgumentEvent;
+import org.apache.causeway.applib.services.wrapper.events.ActionInvocationEvent;
+import org.apache.causeway.applib.services.wrapper.events.ActionUsabilityEvent;
+import org.apache.causeway.applib.services.wrapper.events.ActionVisibilityEvent;
+import org.apache.causeway.applib.services.wrapper.events.InteractionEvent;
+import org.apache.causeway.applib.services.wrapper.events.PropertyModifyEvent;
+import org.apache.causeway.applib.services.wrapper.events.PropertyUsabilityEvent;
+import org.apache.causeway.applib.services.wrapper.events.PropertyVisibilityEvent;
+import org.apache.causeway.core.config.CausewayConfiguration;
import org.apache.causeway.core.metamodel.commons.UtilStr;
+import org.apache.causeway.core.metamodel.consent.Consent;
+
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@@ -103,6 +119,7 @@ public class CommandExecutorServiceDefault implements CommandExecutorService {
@Inject Provider<CommandPublisher> commandPublisherProvider;
@Inject @Getter final SpecificationLoader specificationLoader;
+ @Autowired private CausewayConfiguration causewayConfiguration;
@Override
public Try<Bookmark> executeCommand(final Command command) {
@@ -194,6 +211,7 @@ private Bookmark doExecuteCommand(final CommandDto dto) {
val targetOidDtoList = oidsDto.getOid();
val interactionType = memberDto.getInteractionType();
+ final var interactionInitiatedBy = InteractionInitiatedBy.FRAMEWORK;
if(interactionType == InteractionType.ACTION_INVOCATION) {
val actionDto = (ActionDto) memberDto;
@@ -209,8 +227,37 @@ private Bookmark doExecuteCommand(final CommandDto dto) {
val argAdapters = argAdaptersFor(actionDto, objectAction);
val interactionHead = objectAction.interactionHead(targetAdapter);
+ final var headTarget = interactionHead.getTarget(); // takes into account whether regular or mixin
+
+ final var interactionAdvisorPolicy = getInteractionAdvisorPolicy();
+ switch(interactionAdvisorPolicy) {
+ case CHECK:
+ final var visibility = objectAction.isVisible(headTarget, interactionInitiatedBy, Where.ANYWHERE);
+ if(visibility.isVetoed()) {
+ throw new HiddenException(new ActionVisibilityEvent(headTarget.getPojo(), objectAction.getFeatureIdentifier()));
+ }
+ final var usability = objectAction.isUsable(headTarget, interactionInitiatedBy, Where.ANYWHERE);
+ if(usability.isVetoed()) {
+ throw new DisabledException(new ActionUsabilityEvent(headTarget.getPojo(), objectAction.getFeatureIdentifier()));
+ }
+ // this checks each arg individually, then all of them as a set.
+ final var argumentsValidity = objectAction.isArgumentSetValid(interactionHead, argAdapters, interactionInitiatedBy);
+ if(argumentsValidity.isVetoed()) {
+ // ActionInvocationEvent is somewhat odd as the underlying event, but it is what WrapperFactory does...
+ throw new InvalidException(new ActionInvocationEvent(headTarget.getPojo(), objectAction.getFeatureIdentifier(), argAdapters.stream().map(ManagedObject::getPojo).toArray()));
+ }
+ break;
+ case CHECK_BUT_IGNORE:
+ final var visibleIgnored = objectAction.isVisible(headTarget, interactionInitiatedBy, Where.ANYWHERE);
+ final var usableIgnored = objectAction.isUsable(headTarget, interactionInitiatedBy, Where.ANYWHERE);
+ final var argumentSetValidIgnored = objectAction.isArgumentSetValid(interactionHead, argAdapters, interactionInitiatedBy);
+ break;
+ case NO_CHECK:
+ break;
+ }
+
- val resultAdapter = objectAction.execute(interactionHead, argAdapters, InteractionInitiatedBy.FRAMEWORK);
+ val resultAdapter = objectAction.execute(interactionHead, argAdapters, interactionInitiatedBy);
// flush any PersistenceCommands pending
// (else might get transient objects for the return value)
@@ -244,7 +291,31 @@ private Bookmark doExecuteCommand(final CommandDto dto) {
val property = findOneToOneAssociation(targetAdapter, logicalMemberIdentifier);
val newValueAdapter = valueMarshaller.recoverPropertyFrom(propertyDto);
- property.set(targetAdapter, newValueAdapter, InteractionInitiatedBy.FRAMEWORK);
+ final var interactionAdvisorPolicy = getInteractionAdvisorPolicy();
+ switch(interactionAdvisorPolicy) {
+ case CHECK:
+ final var visibility = property.isVisible(targetAdapter, interactionInitiatedBy, Where.ANYWHERE);
+ if(visibility.isVetoed()) {
+ throw new HiddenException(new PropertyVisibilityEvent(targetAdapter.getPojo(), property.getFeatureIdentifier()));
+ }
+ final var usability = property.isUsable(targetAdapter, interactionInitiatedBy, Where.ANYWHERE);
+ if(usability.isVetoed()) {
+ throw new DisabledException(new PropertyUsabilityEvent(targetAdapter.getPojo(), property.getFeatureIdentifier()));
+ }
+ final var validity = property.isAssociationValid(targetAdapter, newValueAdapter, interactionInitiatedBy);
+ if(validity.isVetoed()) {
+ throw new InvalidException(new PropertyModifyEvent(targetAdapter.getPojo(), property.getFeatureIdentifier(), newValueAdapter.getPojo()));
+ }
+ break;
+ case CHECK_BUT_IGNORE:
+ final var visibilityIgnored = property.isVisible(targetAdapter, interactionInitiatedBy, Where.ANYWHERE);
+ final var usabilityIgnored = property.isUsable(targetAdapter, interactionInitiatedBy, Where.ANYWHERE);
+ final var validityIgnored = property.isAssociationValid(targetAdapter, newValueAdapter, interactionInitiatedBy);
+ break;
+ case NO_CHECK:
+ break;
+ }
+ property.set(targetAdapter, newValueAdapter, interactionInitiatedBy);
// there is no return value for property modifications.
}
@@ -252,6 +323,10 @@ private Bookmark doExecuteCommand(final CommandDto dto) {
return null;
}
+ private CausewayConfiguration.Core.RuntimeServices.CommandExecutorService.InteractionAdvisorPolicy getInteractionAdvisorPolicy() {
+ return causewayConfiguration.getCore().getRuntimeServices().getCommandExecutorService().getInteractionAdvisorPolicy();
+ }
+
private String targetBookmarkStrFor(CommandDto dto) {
return dto.getTargets().getOid().stream()
.map(oidDto -> UtilStr.entityAsStr(Bookmark.forOidDto(oidDto), specificationLoader))
@@ -349,7 +424,7 @@ private String argStr(Identifier actionIdentifier, int i, ParamDto paramDto) {
return UtilStr.namedArgStr(paramName, argValue);
}
-//not used
+//not used
// private static boolean isSensitiveName(String name) {
// return name.equalsIgnoreCase("password") ||
// name.equalsIgnoreCase("secret") ||