This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new 2fbe045e96e CAUSEWAY-3883: converts SyncControl and AsyncControl to
records
2fbe045e96e is described below
commit 2fbe045e96e01913cc1acfe83084a87cae4b842d
Author: Andi Huber <[email protected]>
AuthorDate: Wed Jun 25 22:02:50 2025 +0200
CAUSEWAY-3883: converts SyncControl and AsyncControl to records
---
.../services/wrapper/control/AsyncControl.java | 204 +++++++++++----------
.../services/wrapper/control/AsyncLogger.java | 55 ++++++
.../services/wrapper/control/ControlAbstract.java | 87 ---------
.../services/wrapper/control/ExecutionMode.java | 44 -----
.../services/wrapper/control/SyncControl.java | 103 ++++++-----
.../wrapper/control/AsyncControl_Test.java | 40 ++--
.../services/wrapper/control/SyncControl_Test.java | 33 ++--
.../runtime/wrap/WrapperInvocationHandler.java | 5 +-
.../wrapper/WrapperFactoryDefault.java | 198 ++++++++++----------
.../handlers/DomainObjectInvocationHandler.java | 12 +-
.../wrapper/WrapperFactoryDefaultTest.java | 4 +-
.../BackgroundService_IntegTestAbstract.java | 6 +-
.../testdomain/interact/CommandArgumentTest.java | 2 +-
13 files changed, 367 insertions(+), 426 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl.java
index c2054f52adb..e59fdeef199 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl.java
@@ -21,10 +21,12 @@
import java.time.ZoneId;
import java.util.Locale;
import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -34,7 +36,6 @@
import org.apache.causeway.applib.services.wrapper.WrapperFactory;
import org.apache.causeway.commons.internal.assertions._Assert;
-import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
@@ -43,20 +44,49 @@
* {@link org.apache.causeway.applib.services.wrapper.WrapperFactory} is
actually
* executed.
*
- * <p>
- * Executing in a separate thread means that the target and arguments are
- * used in a new {@link
org.apache.causeway.applib.services.iactn.Interaction}
- * (and transaction). If any of these are entities, they are retrieved
- * from the database afresh; it isn't possible to pass domain entity
- * references from the foreground calling thread to the background threads.
- * </p>
+ * <p> Executing in a separate thread means that the target and arguments are
+ * used in a new {@link org.apache.causeway.applib.services.iactn.Interaction}
+ * (and transaction). If any of these are entities, they are retrieved
+ * from the database afresh; it isn't possible to pass domain entity
+ * references from the foreground calling thread to the background threads.
*
* @param <R> - return value.
*
* @since 2.0 {@index}
*/
@Log4j2
-public class AsyncControl<R> extends ControlAbstract<AsyncControl<R>> {
+public record AsyncControl<R>(
+ Class<R> returnType,
+ SyncControl syncControl,
+ @Nullable ExecutorService executorService,
+
+ /**
+ * Defaults to the system clock, if not overridden
+ */
+ @Nullable VirtualClock clock,
+ /**
+ * Defaults to the system locale, if not overridden
+ */
+ @Nullable Locale locale,
+ /**
+ * Defaults to the system time zone, if not overridden
+ */
+ @Nullable ZoneId timeZone,
+ /**
+ * Specifies the user for the session used to execute the command
+ * asynchronously, in the background.
+ *
+ * <p>If not specified, then the user of the current foreground
session is used.
+ */
+ @Nullable UserMemento user,
+ /**
+ * Contains the result of the invocation.
+ *
+ * <p> If an entity is returned, then the object is automatically
detached
+ * because the persistence session within which it was obtained will
have
+ * been closed already.
+ */
+ AtomicReference<Future<R>> futureRef) {
/**
* Factory method to instantiate a control instance for a void action
@@ -71,131 +101,104 @@ public static AsyncControl<Void> returningVoid() {
* Factory method to instantiate for a control instance for an action
* returning a value of `<R>` (where this value will be returned through
* the `Future`).
- *
- * @param cls
- * @param <X>
*/
public static <X> AsyncControl<X> returning(final Class<X> cls) {
return new AsyncControl<X>(cls);
}
- @Getter
- private final Class<R> returnType;
-
+ // non canonical constructor
private AsyncControl(final Class<R> returnType) {
- this.returnType = returnType;
- with(exception -> {
- log.error(logMessage(), exception);
- return null;
- });
+ this(returnType,
+ SyncControl.control(),
+ /*executorService*/null, /*clock*/null, /*locale*/null,
/*timeZone*/null, /*user*/null,
+ new AtomicReference<>());
+ }
+
+ /**
+ * Explicitly set the action to be executed.
+ */
+ public AsyncControl<R> withExecute() {
+ return new AsyncControl<>(returnType, syncControl.withExecute(),
executorService, clock, locale, timeZone, user, futureRef);
+ }
+
+ /**
+ * Explicitly set the action to <i>not</i >be executed, in other words a
+ * "dry run".
+ */
+ public AsyncControl<R> withNoExecute() {
+ return new AsyncControl<>(returnType, syncControl.withExecute(),
executorService, clock, locale, timeZone, user, futureRef);
}
/**
* Skip checking business rules (hide/disable/validate) before
* executing the underlying property or action
*/
- @Override
public AsyncControl<R> withSkipRules() {
- return super.withSkipRules();
+ return new AsyncControl<>(returnType, syncControl.withSkipRules(),
executorService, clock, locale, timeZone, user, futureRef);
+ }
+
+ public AsyncControl<R> withCheckRules() {
+ return new AsyncControl<>(returnType, syncControl.withCheckRules(),
executorService, clock, locale, timeZone, user, futureRef);
}
/**
- * How to handle exceptions if they occur, using the provided
- * {@link ExceptionHandler}.
+ * How to handle exceptions if they occur, using the provided {@link
ExceptionHandler}.
*
- * <p>
- * The default behaviour is to rethrow the exception.
- * </p>
+ * <p>The default behaviour is to rethrow the exception.
+ *
+ * <p>Changes are made in place, returning the same instance.
*/
- @Override
- public AsyncControl<R> with(final ExceptionHandler exceptionHandler) {
- return super.with(exceptionHandler);
+ public AsyncControl<R> setExceptionHandler(final @NonNull ExceptionHandler
exceptionHandler) {
+ syncControl.setExceptionHandler(exceptionHandler);
+ return this;
}
- @Getter @Nullable
- private ExecutorService executorService = null;
-
/**
* Specifies the {@link ExecutorService} to use to obtain the thread
* to invoke the action.
- * <p>
- * The default is {@code null}, indicating, that its the {@link
WrapperFactory}'s
+ *
+ * <p>The default is {@code null}, indicating, that its the {@link
WrapperFactory}'s
* responsibility to provide a suitable {@link ExecutorService}.
*
* @param executorService - null-able
*/
public AsyncControl<R> with(final ExecutorService executorService) {
- this.executorService = executorService;
- return this;
- // ...
+ return new AsyncControl<>(returnType, syncControl, executorService,
clock, locale, timeZone, user, futureRef);
}
/**
* Defaults to the system clock, if not overridden
*/
- @Getter
- private VirtualClock clock;
public AsyncControl<R> withClock(final @NonNull VirtualClock clock) {
- this.clock = clock;
- return this;
- // ...
+ return new AsyncControl<>(returnType, syncControl, executorService,
clock, locale, timeZone, user, futureRef);
}
/**
* Defaults to the system locale, if not overridden
*/
- @Getter
- private Locale locale;
public AsyncControl<R> withLocale(final @NonNull Locale locale) {
- this.locale = locale;
- return this;
- // ...
+ return new AsyncControl<>(returnType, syncControl, executorService,
clock, locale, timeZone, user, futureRef);
}
/**
* Defaults to the system time zone, if not overridden
*/
- @Getter
- private ZoneId timeZone;
public AsyncControl<R> withTimeZone(final @NonNull ZoneId timeZone) {
- this.timeZone = timeZone;
- return this;
- // ...
+ return new AsyncControl<>(returnType, syncControl, executorService,
clock, locale, timeZone, user, futureRef);
}
- @Getter
- private UserMemento user;
/**
* Specifies the user for the session used to execute the command
* asynchronously, in the background.
*
- * <p>
- * If not specified, then the user of the current foreground session is
used.
- * </p>
+ * <p>If not specified, then the user of the current foreground session is
used.
*/
public AsyncControl<R> withUser(final @NonNull UserMemento user) {
- this.user = user;
- return this;
- // ...
+ return new AsyncControl<>(returnType, syncControl, executorService,
clock, locale, timeZone, user, futureRef);
}
- /**
- * Contains the result of the invocation.
- *
- * <p>
- * If an entity is returned, then the object is automatically detached
- * because the persistence session within which it was obtained will have
- * been closed already.
- * </p>
- */
- @Getter
- private Future<R> future;
-
- /**
- * For framework use only.
- */
- public void setFuture(final Future<R> future) {
- this.future = future;
+ public Future<R> future() {
+ return futureRef.get();
}
/**
@@ -209,26 +212,39 @@ public void setFuture(final Future<R> future) {
* @throws InterruptedException if the current thread was interrupted
while waiting
* @throws TimeoutException if the wait timed out
*/
+ @SuppressWarnings("javadoc")
@SneakyThrows
public R waitForResult(final long timeout, final TimeUnit unit) {
- _Assert.assertNotNull(future,
+ _Assert.assertNotNull(future(),
()->"detected call to waitForResult(..) before future was
set");
- return future.get(timeout, unit);
+ return future().get(timeout, unit);
}
- private String logMessage() {
- StringBuilder buf = new StringBuilder("Failed to execute ");
- if(getMethod() != null) {
- buf.append(" ").append(getMethod().getName()).append(" ");
- if(getBookmark() != null) {
- buf.append(" on '")
- .append(getBookmark().logicalTypeName())
- .append(":")
- .append(getBookmark().identifier())
- .append("'");
- }
- }
- return buf.toString();
- }
+ // -- DEPRECATIONS
+
+ @Deprecated public Class<R> getReturnType() { return returnType(); }
+ @Deprecated public ExecutorService getExecutorService() { return
executorService(); }
+
+ /**
+ * Defaults to the system clock, if not overridden
+ */
+ @Deprecated public VirtualClock getClock() { return clock(); }
+ /**
+ * Defaults to the system locale, if not overridden
+ */
+ @Deprecated public Locale getLocale() { return locale(); }
+ /**
+ * Defaults to the system time zone, if not overridden
+ */
+ @Deprecated public ZoneId getTimeZone() { return timeZone(); }
+ /**
+ * Specifies the user for the session used to execute the command
+ * asynchronously, in the background.
+ *
+ * <p>If not specified, then the user of the current foreground session is
used.
+ */
+ @Deprecated public UserMemento getUser() { return user(); }
+
+ @Deprecated public Future<R> getFuture() { return future(); }
}
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncLogger.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncLogger.java
new file mode 100644
index 00000000000..e736f03c591
--- /dev/null
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/AsyncLogger.java
@@ -0,0 +1,55 @@
+/*
+ * 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.applib.services.wrapper.control;
+
+import java.lang.reflect.Method;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+
+/**
+ * used for exception logging
+ */
+public record AsyncLogger(ExceptionHandler rootExceptionHandler, Method
method, Bookmark bookmark) implements ExceptionHandler {
+
+ static Logger log =
org.apache.logging.log4j.LogManager.getLogger(AsyncControl.class);
+
+ @Override
+ public Object handle(Exception ex) throws Exception {
+ log(ex);
+ return rootExceptionHandler.handle(ex);
+ }
+
+ void log(Exception ex) {
+ var buf = new StringBuilder("Failed to execute ");
+ if(method() != null) {
+ buf.append(" ").append(method().getName()).append(" ");
+ if(bookmark() != null) {
+ buf.append(" on '")
+ .append(bookmark().logicalTypeName())
+ .append(":")
+ .append(bookmark().identifier())
+ .append("'");
+ }
+ }
+ log.error(buf.toString(), ex);
+ }
+
+}
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ControlAbstract.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ControlAbstract.java
deleted file mode 100644
index 7d886e5a02a..00000000000
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ControlAbstract.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.applib.services.wrapper.control;
-
-import java.lang.reflect.Method;
-import java.util.EnumSet;
-import java.util.Optional;
-
-import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.commons.collections.ImmutableEnumSet;
-import org.apache.causeway.commons.internal.base._Casts;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- *
- * @since 2.0 {@index}
- */
-public class ControlAbstract<T extends ControlAbstract<T>> {
-
- protected ControlAbstract() {
- }
-
- /**
- * Set by framework; simply used for logging purposes.
- */
- @Getter(AccessLevel.PACKAGE) @Setter
- private Method method;
-
- /**
- * Set by framework; simply used for logging purposes.
- */
- @Getter(AccessLevel.PACKAGE) @Setter
- private Bookmark bookmark;
-
- @Getter
- private boolean checkRules = true;
- public T withCheckRules() {
- checkRules = true;
- return _Casts.uncheckedCast(this);
- }
- public T withSkipRules() {
- checkRules = false;
- return _Casts.uncheckedCast(this);
- }
-
- private ExceptionHandler exceptionHandler;
-
- public Optional<ExceptionHandler> getExceptionHandler() {
- return Optional.ofNullable(exceptionHandler);
- }
-
- public T with(final ExceptionHandler exceptionHandler) {
- this.exceptionHandler = exceptionHandler;
- return _Casts.uncheckedCast(this);
- }
-
- /**
- * Not API.
- */
- public ImmutableEnumSet<ExecutionMode> getExecutionModes() {
- EnumSet<ExecutionMode> modes = EnumSet.noneOf(ExecutionMode.class);
- if(!checkRules) {
- modes.add(ExecutionMode.SKIP_RULE_VALIDATION);
- }
- return ImmutableEnumSet.from(modes);
- }
-
-}
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ExecutionMode.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ExecutionMode.java
deleted file mode 100644
index 92c53b66eb6..00000000000
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/ExecutionMode.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.applib.services.wrapper.control;
-
-import org.apache.causeway.applib.services.wrapper.WrapperFactory;
-
-/**
- * Whether interactions with the wrapper are actually passed onto the
- * underlying domain object.
- *
- * @see WrapperFactory#wrap(Object,
org.apache.causeway.applib.services.wrapper.control.SyncControl)
- *
- * @since 2.0 {@index}
- */
-public enum ExecutionMode {
- /**
- * Skip all business rules.
- */
- SKIP_RULE_VALIDATION,
- /**
- * Skip actual execution.
- *
- * <p>
- * This is not supported for {@link WrapperFactory#asyncWrap(Object,
AsyncControl)};
- * instead just invoke {@link WrapperFactory#wrap(Object, SyncControl)}.
- */
- SKIP_EXECUTION,
-}
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/SyncControl.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/SyncControl.java
index 0dafa7a8d65..38d3f235a10 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/SyncControl.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/control/SyncControl.java
@@ -18,78 +18,99 @@
*/
package org.apache.causeway.applib.services.wrapper.control;
-import java.util.EnumSet;
+import java.util.concurrent.atomic.AtomicReference;
-import org.apache.causeway.commons.collections.ImmutableEnumSet;
+import org.jspecify.annotations.Nullable;
+
+import lombok.NonNull;
/**
* Controls the way that a (synchronous) wrapper works.
*
- * @since 2.0 {@index}
+ * @since 2.0 revised for 3.4 {@index}
*/
-public class SyncControl extends ControlAbstract<SyncControl> {
+public record SyncControl(
+ /**
+ * How to handle exceptions if they occur, using the provided
+ * {@link ExceptionHandler}.
+ *
+ * <p>The default behaviour is to rethrow the exception.
+ */
+ AtomicReference<ExceptionHandler> exceptionHandlerRef,
+ boolean isSkipExecute,
+ /**
+ * Skip checking business rules (hide/disable/validate) before
+ * executing the underlying property or action
+ */
+ boolean isSkipRules) {
public static SyncControl control() {
- return new SyncControl();
+ return new SyncControl(null, false, false);
}
- private SyncControl() {
- with(exception -> {
- throw exception;
- });
+ public SyncControl(
+ @Nullable AtomicReference<ExceptionHandler> exceptionHandlerRef,
+ boolean isSkipExecute,
+ boolean isSkipRules) {
+ this.exceptionHandlerRef = exceptionHandlerRef!=null
+ ? exceptionHandlerRef
+ : new AtomicReference<>();
+ this.isSkipExecute = isSkipExecute;
+ this.isSkipRules = isSkipRules;
+ if(this.exceptionHandlerRef.get()==null) {
+ this.exceptionHandlerRef.set(exception -> { throw exception; });
+ }
}
/**
- * Skip checking business rules (hide/disable/validate) before
- * executing the underlying property or action
+ * Explicitly set the action to be executed.
*/
- @Override
- public SyncControl withSkipRules() {
- return super.withSkipRules();
+ public SyncControl withExecute() {
+ return new SyncControl(exceptionHandlerRef, false, isSkipRules);
}
/**
- * How to handle exceptions if they occur, using the provided
- * {@link ExceptionHandler}.
- *
- * <p>
- * The default behaviour is to rethrow the exception.
- * </p>
+ * Explicitly set the action to <i>not</i >be executed, in other words a
+ * "dry run".
*/
- @Override
- public SyncControl with(final ExceptionHandler exceptionHandler) {
- return super.with(exceptionHandler);
+ public SyncControl withNoExecute() {
+ return new SyncControl(exceptionHandlerRef, true, isSkipRules);
}
- private boolean execute = true;
-
/**
- * Explicitly set the action to be executed.
+ * Skip checking business rules (hide/disable/validate) before
+ * executing the underlying property or action
*/
- public SyncControl withExecute() {
- execute = true;
- return this;
+ public SyncControl withSkipRules() {
+ return new SyncControl(exceptionHandlerRef, isSkipExecute, true);
+ }
+
+ public SyncControl withCheckRules() {
+ return new SyncControl(exceptionHandlerRef, isSkipExecute, false);
}
/**
- * Explicitly set the action to <i>not</i >be executed, in other words a
- * "dry run".
+ * How to handle exceptions if they occur, using the provided {@link
ExceptionHandler}.
+ *
+ * <p>The default behaviour is to rethrow the exception.
+ *
+ * <p>Changes are made in place, returning the same instance.
*/
- public SyncControl withNoExecute() {
- execute = false;
+ public SyncControl setExceptionHandler(final @NonNull ExceptionHandler
exceptionHandler) {
+ exceptionHandlerRef.set(exceptionHandler);
return this;
}
+ public ExceptionHandler exceptionHandler() {
+ return exceptionHandlerRef.get();
+ }
+
/**
- * Not API.
+ * @return whether this and other share the same execution mode, ignoring
exceptionHandling
*/
- @Override
- public ImmutableEnumSet<ExecutionMode> getExecutionModes() {
- EnumSet<ExecutionMode> modes =
EnumSet.copyOf(super.getExecutionModes().toEnumSet());
- if(!execute) {
- modes.add(ExecutionMode.SKIP_EXECUTION);
- }
- return ImmutableEnumSet.from(modes);
+ public boolean isEquivalent(SyncControl other) {
+ return this.isSkipExecute == other.isSkipExecute
+ && this.isSkipRules == other.isSkipRules;
}
}
diff --git
a/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl_Test.java
b/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl_Test.java
index cad3b32b0c2..6b6d5635864 100644
---
a/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl_Test.java
+++
b/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/AsyncControl_Test.java
@@ -37,7 +37,8 @@ public void defaults() throws Exception {
var control = AsyncControl.returningVoid();
// then
- Assertions.assertThat(control.getExecutionModes()).isEmpty();
+
Assertions.assertThat(control.syncControl().isSkipExecute()).isEqualTo(false);
+
Assertions.assertThat(control.syncControl().isSkipRules()).isEqualTo(false);
}
@Test
@@ -46,10 +47,11 @@ public void check_rules() throws Exception {
var control = AsyncControl.returningVoid();
// when
- control.withCheckRules();
+ control = control.withCheckRules();
// then
- Assertions.assertThat(control.getExecutionModes()).isEmpty();
+
Assertions.assertThat(control.syncControl().isSkipExecute()).isEqualTo(false);
+
Assertions.assertThat(control.syncControl().isSkipRules()).isEqualTo(false);
}
@Test
@@ -59,10 +61,11 @@ public void skip_rules() throws Exception {
var control = AsyncControl.returningVoid();
// when
- control.withSkipRules();
+ control = control.withSkipRules();
// then
-
Assertions.assertThat(control.getExecutionModes()).contains(ExecutionMode.SKIP_RULE_VALIDATION);
+
Assertions.assertThat(control.syncControl().isSkipExecute()).isEqualTo(false);
+
Assertions.assertThat(control.syncControl().isSkipRules()).isEqualTo(true);
}
@Test
@@ -72,10 +75,10 @@ public void user() throws Exception {
var control = AsyncControl.returningVoid();
// when
- control.withUser(UserMemento.ofName("fred"));
+ control = control.withUser(UserMemento.ofName("fred"));
// then
- Assertions.assertThat(control.getUser().name()).isEqualTo("fred");
+ Assertions.assertThat(control.user().name()).isEqualTo("fred");
}
@Test
@@ -85,32 +88,29 @@ public void roles() throws Exception {
var control = AsyncControl.returningVoid();
// when
- control.withUser(UserMemento.ofNameAndRoleNames("fred", "role-1",
"role-2"));
+ control = control.withUser(UserMemento.ofNameAndRoleNames("fred",
"role-1", "role-2"));
// then
-
Assertions.assertThat(control.getUser().streamRoleNames().collect(Collectors.toList()))
+
Assertions.assertThat(control.user().streamRoleNames().collect(Collectors.toList()))
.containsExactlyInAnyOrder("role-1", "role-2");
}
@Test
public void chaining() throws Exception {
- var executorService = new ExecutorServiceAdapter(new
TaskExecutorAdapter(command -> {
- }));
- ExceptionHandler exceptionHandler = ex -> null;
+ var executorService = new ExecutorServiceAdapter(new
TaskExecutorAdapter(command -> {}));
+ var exceptionHandler = (ExceptionHandler) ex -> null;
var control = AsyncControl.returning(String.class)
.withSkipRules()
.withUser(UserMemento.ofNameAndRoleNames("fred", "role-1",
"role-2"))
.with(executorService)
- .with(exceptionHandler);
-
- Assertions.assertThat(control.getExecutionModes())
- .containsExactlyInAnyOrder(ExecutionMode.SKIP_RULE_VALIDATION);
- Assertions.assertThat(control.getExecutorService())
- .isSameAs(executorService);
- Assertions.assertThat(control.getExceptionHandler().orElse(null))
- .isSameAs(exceptionHandler);
+ .setExceptionHandler(exceptionHandler);
+
+
Assertions.assertThat(control.syncControl().isSkipExecute()).isEqualTo(false);
+
Assertions.assertThat(control.syncControl().isSkipRules()).isEqualTo(true);
+
Assertions.assertThat(control.executorService()).isSameAs(executorService);
+
Assertions.assertThat(control.syncControl().exceptionHandler()).isSameAs(exceptionHandler);
}
}
\ No newline at end of file
diff --git
a/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/SyncControl_Test.java
b/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/SyncControl_Test.java
index 7de51582617..fc98b02e18f 100644
---
a/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/SyncControl_Test.java
+++
b/api/applib/src/test/java/org/apache/causeway/applib/services/wrapper/control/SyncControl_Test.java
@@ -20,10 +20,9 @@
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import org.apache.causeway.commons.internal.base._NullSafe;
-
class SyncControl_Test {
@Test
@@ -33,7 +32,8 @@ public void defaults() throws Exception {
var control = SyncControl.control();
// then
- assertTrue(_NullSafe.isEmpty(control.getExecutionModes()));
+ assertFalse(control.isSkipExecute());
+ assertFalse(control.isSkipRules());
}
@Test
@@ -42,10 +42,11 @@ public void check_rules() throws Exception {
var control = SyncControl.control();
// when
- control.withCheckRules();
+ control = control.withCheckRules();
// then
- assertTrue(_NullSafe.isEmpty(control.getExecutionModes()));
+ assertFalse(control.isSkipExecute());
+ assertFalse(control.isSkipRules());
}
@Test
@@ -55,10 +56,11 @@ public void skip_rules() throws Exception {
var control = SyncControl.control();
// when
- control.withSkipRules();
+ control = control.withSkipRules();
// then
-
assertTrue(control.getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION));
+ assertFalse(control.isSkipExecute());
+ assertTrue(control.isSkipRules());
}
@Test
@@ -68,10 +70,11 @@ public void execute() throws Exception {
var control = SyncControl.control();
// when
- control.withExecute();
+ control = control.withExecute();
// then
- assertTrue(_NullSafe.isEmpty(control.getExecutionModes()));
+ assertFalse(control.isSkipExecute());
+ assertFalse(control.isSkipRules());
}
@Test
@@ -81,10 +84,11 @@ public void no_execute() throws Exception {
var control = SyncControl.control();
// when
- control.withNoExecute();
+ control = control.withNoExecute();
// then
-
assertTrue(control.getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION));
+ assertTrue(control.isSkipExecute());
+ assertFalse(control.isSkipRules());
}
@Test
@@ -95,11 +99,10 @@ public void chaining() throws Exception {
var control = SyncControl.control()
.withNoExecute()
.withSkipRules()
- .with(exceptionHandler);
+ .setExceptionHandler(exceptionHandler);
- assertTrue(control.getExecutionModes().size()==2);
-
assertTrue(control.getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION));
-
assertTrue(control.getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION));
+ assertTrue(control.isSkipExecute());
+ assertTrue(control.isSkipRules());
}
}
\ No newline at end of file
diff --git
a/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
b/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
index fbe759c1843..90fd46f407a 100644
---
a/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
+++
b/core/runtime/src/main/java/org/apache/causeway/core/runtime/wrap/WrapperInvocationHandler.java
@@ -24,7 +24,6 @@
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
-import org.apache.causeway.applib.services.wrapper.control.ExecutionMode;
import org.apache.causeway.commons.internal._Constants;
import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.commons.internal.proxy.CachableInvocationHandler;
@@ -119,11 +118,11 @@ static WrapperInvocation of(Object target, Method method,
Object[] args) {
}
public boolean shouldEnforceRules() {
- return
!origin().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION);
+ return !origin().syncControl().isSkipRules();
}
public boolean shouldExecute() {
- return
!origin().syncControl().getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION);
+ return !origin().syncControl().isSkipExecute();
}
}
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
index 84015204d17..b155151ec7c 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
@@ -22,7 +22,6 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -61,7 +60,7 @@
import org.apache.causeway.applib.services.wrapper.WrapperFactory;
import org.apache.causeway.applib.services.wrapper.callable.AsyncCallable;
import org.apache.causeway.applib.services.wrapper.control.AsyncControl;
-import org.apache.causeway.applib.services.wrapper.control.ExecutionMode;
+import org.apache.causeway.applib.services.wrapper.control.AsyncLogger;
import org.apache.causeway.applib.services.wrapper.control.SyncControl;
import org.apache.causeway.applib.services.wrapper.events.ActionArgumentEvent;
import
org.apache.causeway.applib.services.wrapper.events.ActionInvocationEvent;
@@ -80,7 +79,6 @@
import
org.apache.causeway.applib.services.wrapper.events.PropertyVisibilityEvent;
import
org.apache.causeway.applib.services.wrapper.listeners.InteractionListener;
import org.apache.causeway.applib.services.xactn.TransactionService;
-import org.apache.causeway.commons.collections.ImmutableEnumSet;
import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.collections._Lists;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
@@ -109,7 +107,6 @@
import static
org.apache.causeway.applib.services.wrapper.control.SyncControl.control;
-import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -200,7 +197,7 @@ public <T> T wrap(
if (isWrapper(domainObject)) {
var wrapperObject = (WrappingObject) domainObject;
var origin = wrapperObject.__causeway_origin();
- if(equivalent(origin.syncControl().getExecutionModes(),
syncControl.getExecutionModes())) {
+ if(origin.syncControl().isEquivalent(syncControl)) {
return domainObject;
}
var underlyingDomainObject =
wrapperObject.__causeway_origin().pojo();
@@ -209,14 +206,6 @@ public <T> T wrap(
return createProxy(domainObject, syncControl);
}
- private static boolean equivalent(final ImmutableEnumSet<ExecutionMode>
first, final ImmutableEnumSet<ExecutionMode> second) {
- return equivalent(first.toEnumSet(), second.toEnumSet());
- }
-
- private static boolean equivalent(final EnumSet<ExecutionMode> first,
final EnumSet<ExecutionMode> second) {
- return first.containsAll(second) && second.containsAll(first);
- }
-
@Override
public <T> T wrapMixin(
final @NonNull Class<T> mixinClass,
@@ -240,7 +229,7 @@ public <T> T wrapMixin(
getServiceInjector().injectServicesInto(underlyingMixee);
- if(equivalent(origin.syncControl().getExecutionModes(),
syncControl.getExecutionModes())) {
+ if(origin.syncControl().isEquivalent(syncControl)) {
return mixin;
}
return _Casts.uncheckedCast(createMixinProxy(underlyingMixee,
mixin, syncControl));
@@ -279,7 +268,7 @@ public <T> T unwrap(final T possibleWrappedDomainObject) {
// -- ASYNC WRAPPING
@Override
- public <T,R> T asyncWrap(
+ public <T, R> T asyncWrap(
final @NonNull T domainObject,
final AsyncControl<R> asyncControl) {
@@ -289,7 +278,7 @@ public <T,R> T asyncWrap(
+ "use WrapperFactory.asyncWrapMixin(...) instead");
}
- final InvocationHandler handler = (proxy, method, args) -> {
+ var handler = (InvocationHandler) (proxy, method, args) -> {
var resolvedMethod = _GenericResolver.resolveMethod(method,
domainObject.getClass())
.orElseThrow(); // fail early on attempt to invoke method
that is not part of the meta-model
@@ -297,17 +286,19 @@ public <T,R> T asyncWrap(
return method.invoke(domainObject, args);
}
- if (asyncControl.isCheckRules()) {
+ if (!asyncControl.syncControl().isSkipRules()) {
var doih = proxyGenerator.handler(targetAdapter.objSpec());
- doih.invoke(domainObject, method, args);
+ var origin = WrappingObject.Origin.fallback(domainObject);
+ doih.invoke(new WrapperInvocation(origin, method, args));
}
- var memberAndTarget = memberAndTargetForRegular(resolvedMethod,
targetAdapter);
- if( ! memberAndTarget.isMemberFound()) {
+ var memberAndTarget = MemberAndTarget.forRegular(resolvedMethod,
targetAdapter);
+ if(!memberAndTarget.isMemberFound()) {
return method.invoke(domainObject, args);
}
- return submitAsync(memberAndTarget, args, asyncControl);
+ submitAsync(memberAndTarget, args, asyncControl);
+ return null;
};
@SuppressWarnings("unchecked")
@@ -332,7 +323,7 @@ public <T, R> T asyncWrapMixin(
var mixinConstructor =
MixinConstructor.PUBLIC_SINGLE_ARG_RECEIVING_MIXEE
.getConstructorElseFail(mixinClass, mixee.getClass());
- final InvocationHandler handler = (proxy, method, args) -> {
+ var handler = (InvocationHandler) (proxy, method, args) -> {
var resolvedMethod = _GenericResolver.resolveMethod(method,
mixinClass)
.orElseThrow(); // fail early on attempt to invoke method
that is not part of the meta-model
@@ -340,18 +331,19 @@ public <T, R> T asyncWrapMixin(
return method.invoke(mixin, args);
}
- if (asyncControl.isCheckRules()) {
+ if (!asyncControl.syncControl().isSkipRules()) {
var doih = proxyGenerator.handler(managedMixin.objSpec());
var origin = WrappingObject.Origin.fallbackMixin(mixin,
managedMixee);
doih.invoke(new WrapperInvocation(origin, method, args));
}
- var actionAndTarget = memberAndTargetForMixin(resolvedMethod,
mixee, managedMixin);
- if (! actionAndTarget.isMemberFound()) {
+ var actionAndTarget = MemberAndTarget.forMixin(resolvedMethod,
mixee, managedMixin);
+ if (!actionAndTarget.isMemberFound()) {
return method.invoke(mixin, args);
}
- return submitAsync(actionAndTarget, args, asyncControl);
+ submitAsync(actionAndTarget, args, asyncControl);
+ return null;
};
var proxyClass = proxyFactoryService
@@ -367,7 +359,7 @@ private boolean isInheritedFromJavaLangObject(final Method
method) {
return method.getDeclaringClass().equals(Object.class);
}
- private <R> Object submitAsync(
+ private <R> void submitAsync(
final MemberAndTarget memberAndTarget,
final Object[] args,
final AsyncControl<R> asyncControl) {
@@ -379,96 +371,46 @@ private <R> Object submitAsync(
var parentCommand =
getInteractionService().currentInteractionElseFail().getCommand();
var parentInteractionId = parentCommand.getInteractionId();
- var targetAdapter = memberAndTarget.getTarget();
- var method = memberAndTarget.getMethod();
+ var targetAdapter = memberAndTarget.target();
+ var method = memberAndTarget.method();
var head = InteractionHead.regular(targetAdapter);
var childInteractionId = interactionIdGenerator.interactionId();
CommandDto childCommandDto;
- switch (memberAndTarget.getType()) {
+ switch (memberAndTarget.type()) {
case ACTION:
- var action = memberAndTarget.getAction();
+ var action = memberAndTarget.action();
var argAdapters =
ManagedObject.adaptParameters(action.getParameters(), _Lists.ofArray(args));
childCommandDto = commandDtoFactory
.asCommandDto(childInteractionId, head, action,
argAdapters);
break;
case PROPERTY:
- var property = memberAndTarget.getProperty();
+ var property = memberAndTarget.property();
var propertyValueAdapter =
ManagedObject.adaptProperty(property, args[0]);
childCommandDto = commandDtoFactory
.asCommandDto(childInteractionId, head, property,
propertyValueAdapter);
break;
default:
// shouldn't happen, already catered for this case previously
- return null;
+ return;
}
var oidDto = childCommandDto.getTargets().getOid().get(0);
- asyncControl.setMethod(method);
- asyncControl.setBookmark(Bookmark.forOidDto(oidDto));
+ var rootExceptionHandler =
asyncControl.syncControl().exceptionHandler();
+ asyncControl.setExceptionHandler(new AsyncLogger(rootExceptionHandler,
method, Bookmark.forOidDto(oidDto)));
- var executorService =
Optional.ofNullable(asyncControl.getExecutorService())
+ var executorService =
Optional.ofNullable(asyncControl.executorService())
.orElse(commonExecutorService);
var asyncTask = getServiceInjector().injectServicesInto(new
AsyncTask<R>(
asyncInteractionContext,
Propagation.REQUIRES_NEW,
childCommandDto,
- asyncControl.getReturnType(),
+ asyncControl.returnType(),
parentInteractionId)); // this command becomes the parent of child
command
var future = executorService.submit(asyncTask);
- asyncControl.setFuture(future);
-
- return null;
- }
-
- private MemberAndTarget memberAndTargetForRegular(
- final ResolvedMethod method,
- final ManagedObject targetAdapter) {
-
- var objectMember =
targetAdapter.objSpec().getMember(method).orElse(null);
- if(objectMember == null) {
- return MemberAndTarget.notFound();
- }
-
- if (objectMember instanceof OneToOneAssociation) {
- return MemberAndTarget.foundProperty((OneToOneAssociation)
objectMember, targetAdapter, method.method());
- }
- if (objectMember instanceof ObjectAction) {
- return MemberAndTarget.foundAction((ObjectAction) objectMember,
targetAdapter, method.method());
- }
-
- throw new UnsupportedOperationException(
- "Only properties and actions can be executed in the background
"
- + "(method " + method.name() + " represents a " +
objectMember.getFeatureType().name() + "')");
- }
-
- private <T> MemberAndTarget memberAndTargetForMixin(
- final ResolvedMethod method,
- final T mixee,
- final ManagedObject mixinAdapter) {
-
- var mixinMember =
mixinAdapter.objSpec().getMember(method).orElse(null);
- if (mixinMember == null) {
- return MemberAndTarget.notFound();
- }
-
- // find corresponding action of the mixee (this is the 'real' target,
the target usable for invocation).
- var mixeeClass = mixee.getClass();
-
- // don't care about anything other than actions
- // (contributed properties and collections are read-only).
- final ObjectAction targetAction =
getSpecificationLoader().specForType(mixeeClass)
- .flatMap(mixeeSpec->mixeeSpec.streamAnyActions(MixedIn.ONLY)
- .filter(act ->
((MixedInMember)act).hasMixinAction((ObjectAction) mixinMember))
- .findFirst()
- )
- .orElseThrow(()->new UnsupportedOperationException(String.format(
- "Could not locate objectAction delegating to mixinAction
id='%s' on mixee class '%s'",
- mixinMember.getId(), mixeeClass.getName())));
-
- return MemberAndTarget.foundAction(targetAction,
getObjectManager().adapt(mixee), method.method());
+ asyncControl.futureRef().set(future);
}
private static <R> InteractionContext interactionContextFrom(
@@ -476,15 +418,25 @@ private static <R> InteractionContext
interactionContextFrom(
final InteractionContext interactionContext) {
return InteractionContext.builder()
-
.clock(Optional.ofNullable(asyncControl.getClock()).orElseGet(interactionContext::getClock))
-
.locale(Optional.ofNullable(asyncControl.getLocale()).map(UserLocale::valueOf).orElse(null))
// if not set in asyncControl use defaults (set override to null)
-
.timeZone(Optional.ofNullable(asyncControl.getTimeZone()).orElseGet(interactionContext::getTimeZone))
-
.user(Optional.ofNullable(asyncControl.getUser()).orElseGet(interactionContext::getUser))
+
.clock(Optional.ofNullable(asyncControl.clock()).orElseGet(interactionContext::getClock))
+
.locale(Optional.ofNullable(asyncControl.locale()).map(UserLocale::valueOf).orElse(null))
// if not set in asyncControl use defaults (set override to null)
+
.timeZone(Optional.ofNullable(asyncControl.timeZone()).orElseGet(interactionContext::getTimeZone))
+
.user(Optional.ofNullable(asyncControl.user()).orElseGet(interactionContext::getUser))
.build();
}
- @Data
- static class MemberAndTarget {
+ record MemberAndTarget(
+ Type type,
+ /**
+ * Populated if and only if {@link #type} is {@link Type#ACTION}.
+ */
+ ObjectAction action,
+ /**
+ * Populated if and only if {@link #type} is {@link Type#PROPERTY}.
+ */
+ OneToOneAssociation property,
+ ManagedObject target,
+ Method method) {
static MemberAndTarget notFound() {
return new MemberAndTarget(Type.NONE, null, null, null, null);
}
@@ -494,6 +446,53 @@ static MemberAndTarget foundAction(final ObjectAction
action, final ManagedObjec
static MemberAndTarget foundProperty(final OneToOneAssociation
property, final ManagedObject target, final Method method) {
return new MemberAndTarget(Type.PROPERTY, null, property, target,
method);
}
+ static MemberAndTarget forRegular(
+ final ResolvedMethod method,
+ final ManagedObject targetAdapter) {
+
+ var objectMember =
targetAdapter.objSpec().getMember(method).orElse(null);
+ if(objectMember == null) {
+ return MemberAndTarget.notFound();
+ }
+ if (objectMember instanceof OneToOneAssociation) {
+ return MemberAndTarget.foundProperty((OneToOneAssociation)
objectMember, targetAdapter, method.method());
+ }
+ if (objectMember instanceof ObjectAction) {
+ return MemberAndTarget.foundAction((ObjectAction)
objectMember, targetAdapter, method.method());
+ }
+
+ throw new UnsupportedOperationException(
+ "Only properties and actions can be executed in the
background "
+ + "(method " + method.name() + " represents a " +
objectMember.getFeatureType().name() + "')");
+ }
+ static <T> MemberAndTarget forMixin(
+ final ResolvedMethod method,
+ final T mixee,
+ final ManagedObject mixinAdapter) {
+
+ var mixinMember =
mixinAdapter.objSpec().getMember(method).orElse(null);
+ if (mixinMember == null) {
+ return MemberAndTarget.notFound();
+ }
+
+ var mmc = mixinAdapter.getMetaModelContext();
+
+ // find corresponding action of the mixee (this is the 'real'
target, the target usable for invocation).
+ var mixeeClass = mixee.getClass();
+
+ // don't care about anything other than actions
+ // (contributed properties and collections are read-only).
+ final ObjectAction targetAction =
mmc.getSpecificationLoader().specForType(mixeeClass)
+ .flatMap(mixeeSpec->mixeeSpec.streamAnyActions(MixedIn.ONLY)
+ .filter(act ->
((MixedInMember)act).hasMixinAction((ObjectAction) mixinMember))
+ .findFirst()
+ )
+ .orElseThrow(()->new UnsupportedOperationException(String.format(
+ "Could not locate objectAction delegating to mixinAction
id='%s' on mixee class '%s'",
+ mixinMember.getId(), mixeeClass.getName())));
+
+ return MemberAndTarget.foundAction(targetAction,
mmc.getObjectManager().adapt(mixee), method.method());
+ }
public boolean isMemberFound() {
return type != Type.NONE;
@@ -504,17 +503,6 @@ enum Type {
PROPERTY,
NONE
}
- private final Type type;
- /**
- * Populated if and only if {@link #type} is {@link Type#ACTION}.
- */
- private final ObjectAction action;
- /**
- * Populated if and only if {@link #type} is {@link Type#PROPERTY}.
- */
- private final OneToOneAssociation property;
- private final ManagedObject target;
- private final Method method;
}
// -- LISTENERS
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
index d9dc132084f..3c39d7c97a1 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -534,16 +534,8 @@ private <X> X runExecutionTask(final WrapperInvocation
wrapperInvocation, final
@SneakyThrows
private Object handleException(WrapperInvocation wrapperInvocation, final
Exception ex) {
- var exceptionHandler =
wrapperInvocation.origin().syncControl().getExceptionHandler()
- .orElse(null);
-
- if(exceptionHandler==null) {
- log.warn("No ExceptionHandler was setup to handle this Exception",
ex);
- }
-
- return exceptionHandler!=null
- ? exceptionHandler.handle(ex)
- : null;
+ var exceptionHandler =
wrapperInvocation.origin().syncControl().exceptionHandler();
+ return exceptionHandler.handle(ex);
}
private Object singleArgUnderlyingElseNull(final Object[] args, final
String name) {
diff --git
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
index 3c78be6e899..a7489e78312 100644
---
a/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
+++
b/core/runtimeservices/src/test/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
@@ -26,9 +26,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import org.apache.causeway.applib.services.wrapper.control.ExecutionMode;
import org.apache.causeway.applib.services.wrapper.control.SyncControl;
import org.apache.causeway.commons.internal.proxy.ProxyFactoryService;
import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
@@ -124,7 +122,7 @@ public void
wrap_ofWrapped_differentMode_delegates_to_createProxy() throws Excep
// then
assertThat(wrappingObject, is(not(domainObject)));
assertThat(createProxyCalledWithDomainObject, is(wrappedObject));
- assertThat(createProxyCalledWithSyncControl.getExecutionModes(),
contains(ExecutionMode.SKIP_RULE_VALIDATION));
+ assertThat(createProxyCalledWithSyncControl.isSkipRules(), is(true));
}
}
diff --git
a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
index 40d9ed4ff1a..6ec96cd451a 100644
---
a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
+++
b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
@@ -107,8 +107,8 @@ void async_using_default_executor_service() {
var control = AsyncControl.returning(Counter.class);
wrapperFactory.asyncWrap(counter,
control).bumpUsingDeclaredAction();
- // wait til done
- control.getFuture().get();
+ // wait till done
+ control.future().get();
}).ifFailureFail();
// then
@@ -127,7 +127,7 @@ void async_using_default_executor_service() {
wrapperFactory.asyncWrapMixin(Counter_bumpUsingMixin.class,
counter, control).act();
// wait til done
- control.getFuture().get();
+ control.future().get();
}).ifFailureFail();
// then
diff --git
a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/CommandArgumentTest.java
b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/CommandArgumentTest.java
index 2ce6655761e..3887ffacd97 100644
---
a/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/CommandArgumentTest.java
+++
b/regressiontests/interact/src/test/java/org/apache/causeway/testdomain/interact/CommandArgumentTest.java
@@ -128,7 +128,7 @@ void listParam_shouldAllowAsyncInvocation() throws
InterruptedException, Executi
wrapperFactory.asyncWrap(commandArgDemo, control)
.list(Arrays.asList(1L, 2L, 3L));
- var stringified = control.getFuture().get(3L,
TimeUnit.DAYS).getResultAsString();
+ var stringified = control.future().get(3L,
TimeUnit.DAYS).getResultAsString();
assertEquals("[1, 2, 3]", stringified);
}